aboutsummaryrefslogtreecommitdiffstats
path: root/scripts
diff options
context:
space:
mode:
authorAaron LI <aaronly.me@outlook.com>2017-02-19 16:26:40 +0800
committerAaron LI <aaronly.me@outlook.com>2017-02-19 16:26:40 +0800
commit2aaf7d8106e7d596654c9ae1cf0fa75fd642f3eb (patch)
treeb524d1fa54a4ad6251d467175b71e9dfea5a6631 /scripts
parent89adbc474890c50963537e8e32bdeff8aa3e10d1 (diff)
downloadchandra-acis-analysis-2aaf7d8106e7d596654c9ae1cf0fa75fd642f3eb.tar.bz2
Setup module 'acispy' and clean 'scripts' directory
Setup a module 'acispy' to hold some generic Python modules for better/easier reuse, and clean up the 'scripts' directory, which will be used to hold the CLI tools.
Diffstat (limited to 'scripts')
-rw-r--r--scripts/acis.py91
-rw-r--r--scripts/context.py16
-rw-r--r--scripts/ds9.py30
-rw-r--r--scripts/region.py251
-rw-r--r--scripts/setup_pfiles.py38
-rw-r--r--scripts/spectrum.py44
6 files changed, 16 insertions, 454 deletions
diff --git a/scripts/acis.py b/scripts/acis.py
deleted file mode 100644
index 91d9fcf..0000000
--- a/scripts/acis.py
+++ /dev/null
@@ -1,91 +0,0 @@
-# Copyright (c) 2017 Weitian LI <liweitianux@live.com>
-# MIT license
-
-"""
-Chandra ACIS utilities
-"""
-
-import math
-import subprocess
-import re
-
-
-class ACIS:
- """
- Chandra ACIS detector properties and utilities.
-
- References
- ----------
- [1] CIAO - dictionary - ACIS (advanced camera for imaging and spectroscopy)
- http://cxc.harvard.edu/ciao/dictionary/acis.html
- [2] CIAO - Dictionary - PI (pulse invariant)
- http://cxc.harvard.edu/ciao/dictionary/pi.html
- """
- # Pixel size
- pixel2arcsec = 0.492 # [arcsec]
- # Number of channels
- nchannel = 1024
- # Channel energy width
- echannel = 14.6 # [eV]
-
- @classmethod
- def energy2channel(self, energy):
- """
- Convert energy [eV] to channel number.
- """
- return math.floor(energy/self.echannel + 1)
-
- @classmethod
- def get_type(self, filepath):
- """
- Determine the Chandra ACIS type (``I`` or ``S``) according the
- active ACIS chips.
-
- Parameters
- ----------
- filepath : str
- Path to the input FITS file
-
- Returns
- -------
- acis_type : str
- ``I`` if ACIS-I, ``S`` if ACIS-S;
- otherwise, ``ValueError`` raised.
- """
- subprocess.check_call(["punlearn", "dmkeypar"])
- detnam = subprocess.check_output([
- "dmkeypar", "infile=%s" % filepath, "keyword=DETNAM", "echo=yes"
- ]).decode("utf-8").strip()
- if re.match(r"^ACIS-0123", detnam):
- return "I"
- elif re.match(r"^ACIS-[0-6]*7", detnam):
- return "S"
- else:
- raise ValueError("unknown chip combination: %s" % detnam)
-
- @classmethod
- def get_chips_str(self, filepath, sep=":"):
- """
- Return a string of the chips of interest according to the
- active ACIS type.
-
- Parameters
- ----------
- filepath : str
- Path to the input FITS file
- sep : str, optional
- Separator to join the chip ranges, e.g., 0:3, 0-3
-
- Returns
- -------
- chips : str
- ``0:3`` if ACIS-I, ``7`` if ACIS-S;
- otherwise, ``ValueError`` raised.
- """
- acis_type = self.get_type(filepath)
- if acis_type == "I":
- return sep.join(["0", "3"])
- elif acis_type == "S":
- return "7"
- else:
- raise ValueError("unknown ACIS type")
diff --git a/scripts/context.py b/scripts/context.py
new file mode 100644
index 0000000..b23b2c8
--- /dev/null
+++ b/scripts/context.py
@@ -0,0 +1,16 @@
+# Copyright (c) 2017 Weitian LI <liweitianux@live.com>
+# MIT license
+
+"""
+Portal to 'acispy' module/package
+"""
+
+import os
+import sys
+
+sys.path.insert(
+ 0,
+ os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
+)
+
+import acispy
diff --git a/scripts/ds9.py b/scripts/ds9.py
deleted file mode 100644
index e8c8e88..0000000
--- a/scripts/ds9.py
+++ /dev/null
@@ -1,30 +0,0 @@
-# Copyright (c) 2017 Weitian LI <liweitianux@live.com>
-# MIT license
-
-"""
-Wrapper function to view FITS files using DS9.
-"""
-
-import subprocess
-
-
-def ds9_view(filename, regfile=None, regformat="ciao", regsystem="physical",
- cmap="he", binfactor=2, scale="linear", smooth=None):
- """
- Wrapper function to view FITS files using DS9.
- """
- cmd = [
- "ds9", filename,
- "-cmap", cmap,
- "-bin", "factor", str(binfactor),
- "-scale", scale,
- ]
- if regfile:
- cmd += [
- "-regions", "format", regformat,
- "-regions", "system", regsystem,
- "-regions", regfile,
- ]
- if smooth:
- cmd += ["-smooth", "yes", "-smooth", "radius", str(smooth)]
- subprocess.check_call(cmd)
diff --git a/scripts/region.py b/scripts/region.py
deleted file mode 100644
index 09e42fa..0000000
--- a/scripts/region.py
+++ /dev/null
@@ -1,251 +0,0 @@
-# Copyright (c) 2017 Weitian LI <liweitianux@live.com>
-# MIT license
-
-"""
-Region utilities.
-
-NOTE:
-Only support the commonly used region shapes in CIAO format.
-While more complex shapes and DS9 format are not considered
-at the moment.
-"""
-
-import os
-import re
-
-
-class Point:
- """
- Point region: point(xc,yc)
- """
- shape = "point"
-
- def __init__(self, xc=None, yc=None):
- if isinstance(xc, str):
- self.parse(regstr=xc)
- else:
- self.xc = xc
- self.yc = yc
-
- def parse(self, regstr):
- g = re.split(r"[(),]", regstr)
- self.xc = float(g[1])
- self.yc = float(g[2])
-
- def __str__(self):
- return "%s(%.4f,%.4f)" % (self.shape, self.xc, self.yc)
-
-
-class Circle:
- """
- Circle region: circle(xc,yc,radius)
- """
- shape = "circle"
-
- def __init__(self, xc=None, yc=None, radius=None):
- if isinstance(xc, str):
- self.parse(regstr=xc)
- else:
- self.xc = xc
- self.yc = yc
- self.radius = radius
-
- def parse(self, regstr):
- g = re.split(r"[(),]", regstr)
- self.xc = float(g[1])
- self.yc = float(g[2])
- self.radius = float(g[3])
-
- def __str__(self):
- return "%s(%.4f,%.4f,%.4f)" % (self.shape, self.xc, self.yc,
- self.radius)
-
-
-class Annulus:
- """
- Annulus region:
- annulus(xc,yc,radius,radius2)
- + xc, yc : center
- + radius, radius2 : inner, outer radius
- """
- shape = "annulus"
-
- def __init__(self, xc=None, yc=None, radius=None, radius2=None):
- if isinstance(xc, str):
- self.parse(regstr=xc)
- else:
- self.xc = xc
- self.yc = yc
- self.radius = radius
- self.radius2 = radius2
-
- def parse(self, regstr):
- g = re.split(r"[(),]", regstr)
- self.xc = float(g[1])
- self.yc = float(g[2])
- self.radius = float(g[3])
- self.radius2 = float(g[4])
-
- def __str__(self):
- return "%s(%.4f,%.4f,%.4f,%.4f)" % (self.shape, self.xc, self.yc,
- self.radius, self.radius2)
-
-
-class Pie:
- """
- Pie region:
- pie(xc,yc,radius,radius2,angle1,angle2)
- + xc, yc : center
- + radius, radius2 : inner, outer radius
- + angle1, angle2: start, end angle [0, 360)
- """
- shape = "pie"
-
- def __init__(self, xc=None, yc=None, radius=None, radius2=None,
- angle1=0.0, angle2=360.0):
- if isinstance(xc, str):
- self.parse(regstr=xc)
- else:
- self.xc = xc
- self.yc = yc
- self.radius = radius
- self.radius2 = radius2
- self.angle1 = angle1
- self.angle2 = angle2
-
- def parse(self, regstr):
- g = re.split(r"[(),]", regstr)
- self.xc = float(g[1])
- self.yc = float(g[2])
- self.radius = float(g[3])
- self.radius2 = float(g[4])
- self.angle1 = float(g[5])
- self.angle2 = float(g[6])
-
- def __str__(self):
- return "%s(%.4f,%.4f,%.4f,%.4f,%.4f,%.4f)" % (
- self.shape, self.xc, self.yc, self.radius, self.radius2,
- self.angle1, self.angle2)
-
-
-class Ellipse:
- """
- Ellipse region:
- ellipse(xc,yc,radius,radius2,rotation)
- + xc, yc : center
- + radius, radius2 : semi-major / semi-minor axis
- + rotation: rotation angle [0, 360)
- """
- shape = "ellipse"
-
- def __init__(self, xc=None, yc=None, radius=None, radius2=None,
- rotation=0.0):
- if isinstance(xc, str):
- self.parse(regstr=xc)
- else:
- self.xc = xc
- self.yc = yc
- self.radius = radius
- self.radius2 = radius2
- self.rotation = rotation
-
- def parse(self, regstr):
- g = re.split(r"[(),]", regstr)
- self.xc = float(g[1])
- self.yc = float(g[2])
- self.radius = float(g[3])
- self.radius2 = float(g[4])
- self.rotation = float(g[5])
-
- def __str__(self):
- return "%s(%.4f,%.4f,%.4f,%.4f,%.4f)" % (
- self.shape, self.xc, self.yc, self.radius,
- self.radius2, self.rotation)
-
-
-class Box:
- """
- Box region: box(xc,yc,width,height,rotation)
- """
- shape = "box"
-
- def __init__(self, xc=None, yc=None, width=None, height=None,
- rotation=0.0):
- if isinstance(xc, str):
- self.parse(regstr=xc)
- else:
- self.xc = xc
- self.yc = yc
- self.width = width
- self.height = height
- self.rotation = rotation
-
- def parse(self, regstr):
- g = re.split(r"[(),]", regstr)
- self.xc = float(g[1])
- self.yc = float(g[2])
- self.width = float(g[3])
- self.height = float(g[4])
- self.rotation = float(g[5])
-
- def __str__(self):
- return "%s(%.4f,%.4f,%.4f,%.4f,%.4f)" % (
- self.shape, self.xc, self.yc, self.width,
- self.height, self.rotation)
-
-
-class Regions:
- """
- Manipulate the region files (CIAO format), as well as parse regions
- from strings.
- """
- REGION_SHAPES = {
- "point": Point,
- "circle": Circle,
- "annulus": Annulus,
- "pie": Pie,
- "ellipse": Ellipse,
- "box": Box,
- }
-
- def __init__(self, regfile=None):
- if regfile:
- self.regions = self.load(regfile)
- else:
- self.regions = []
-
- def load(self, infile):
- regstr = []
- for line in open(infile):
- if not (re.match(r"^\s*#.*$", line) or re.match(r"^\s*$", line)):
- regstr.append(line.strip())
- self.regions = self.parse(regstr)
-
- def save(self, outfile, clobber=False):
- if (not clobber) and os.path.exists(outfile):
- raise OSError("output file already exists: %s" % outfile)
- regstr = [str(reg) for reg in self.regions]
- open(outfile, "w").write("\n".join(regstr) + "\n")
-
- @classmethod
- def parse(self, regstr):
- """
- Parse the given (list of) region string(s), and return the
- corresponding parsed region objects.
- """
- if isinstance(regstr, list):
- return [self.parse_single(reg) for reg in regstr]
- else:
- return self.parse_single(regstr)
-
- @classmethod
- def parse_single(self, regstr):
- """
- Parse the given single region string to its corresponding
- region object.
- """
- shape = regstr.strip().split('(')[0].lower()
- if shape in self.REGION_SHAPES:
- return self.REGION_SHAPES[shape](regstr)
- else:
- raise ValueError("unknown region shape: %s" % shape)
diff --git a/scripts/setup_pfiles.py b/scripts/setup_pfiles.py
deleted file mode 100644
index 8a80a14..0000000
--- a/scripts/setup_pfiles.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# Copyright (c) 2017 Weitian LI <liweitianux@live.com>
-# MIT license
-#
-# Weitian LI
-# 2017-02-06
-
-"""
-Prepare the CIAO parameter files and setup the PFILES environment
-variable to keep the pfiles locally, in order to avoid the conflicts
-between multiple instance of the same CIAO tools.
-"""
-
-import os
-import subprocess
-import shutil
-
-
-def setup_pfiles(tools):
- """
- Copy the parameter files of the specified tools to the current
- working directory, and setup the ``PFILES`` environment variable.
-
- Parameters
- ----------
- tools : list[str]
- Name list of the tools to be set up
- """
- for tool in tools:
- pfile = subprocess.check_output([
- "paccess", tool
- ]).decode("utf-8").strip()
- subprocess.check_call(["punlearn", tool])
- try:
- shutil.copy(pfile, ".")
- except shutil.SameFileError:
- pass
- # Setup the ``PFILES`` environment variable
- os.environ["PFILES"] = "./:" + os.environ["PFILES"]
diff --git a/scripts/spectrum.py b/scripts/spectrum.py
deleted file mode 100644
index ad38b0d..0000000
--- a/scripts/spectrum.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# Copyright (c) 2017 Weitian LI <liweitianux@live.com>
-# MIT license
-
-"""
-Chandra ACIS spectrum.
-"""
-
-
-from astropy.io import fits
-
-from acis import ACIS
-
-
-class Spectrum:
- """
- Chandra ACIS spectrum
- """
- def __init__(self, filepath):
- self.filepath = filepath
- self.fitsobj = fits.open(filepath)
- ext_spec = self.fitsobj["SPECTRUM"]
- self.header = ext_spec.header
- # spectral data
- self.channel = ext_spec.data.columns["CHANNEL"].array
- self.counts = ext_spec.data.columns["COUNTS"].array
- # spectral keywords
- self.EXPOSURE = self.header.get("EXPOSURE")
- self.BACKSCAL = self.header.get("BACKSCAL")
-
- def calc_pb_flux(self, elow=9500, ehigh=12000):
- """
- Calculate the particle background flux:
- flux = counts / exposure / area
-
- Parameters
- ----------
- elow, ehigh : float, optional
- Lower and upper energy limit for the particle background.
- """
- chlow = ACIS.energy2channel(elow)
- chhigh = ACIS.energy2channel(ehigh)
- counts = self.counts[(chlow-1):chhigh].sum()
- flux = counts / self.EXPOSURE / self.BACKSCAL
- return flux