Source code for pyspextools.io.pha2

#!/usr/bin/env python

import pyspextools.messages as message
import numpy as np
import math
import astropy.io.fits as fits

from .pha import Pha


[docs]class Pha2: """ Class to read PHA2 type OGIP spectra. :ivar NumberSpectra: Number of spectra in PHAII file. :vartype NumberSpectra: int :ivar phalist: List of PHA spectra. :vartype phalist: list :ivar tg_m: Array of order numbers. :vartype tg_m: numpy.ndarray :ivar tg_part: Array of grating numbers. :vartype tg_part: numpy.ndarray :ivar instrument: Instrument name. :vartype instrument: str :ivar telescope: Telescope name. :vartype telescope: str :ivar grating: Grating name :vartype grating: str :ivar gratings: Dictionary of grating names. :vartype gratings: dict """ def __init__(self): self.NumberSpectra = 0 # Number of spectra in PHAII file self.phalist = [] # List of PHA spectra self.tg_m = np.array([]) # Array of order numbers self.tg_part = np.array([]) # Array of grating numbers self.instrument = '' # Instrument name self.telescope = '' # Telescope name self.grating = '' # Grating name self.gratings = {'1': 'heg', '2': 'meg', '3': 'leg'}
[docs] def read(self, phafile, force_poisson=True, background=False): """Read a type II pha file. Many time Gehrels errors are provided, but we prefer Poisson. Therefore, the optional 'force_poisson' flag is True by default. Set force_poisson to false to obtain the errors from the file. If the user wants to subtract the background, the flag 'background' should be set to True. :param phafile: Name of the type II PHA file. :type phafile: str :param force_poisson: Flag to set the enforcement of Poisson errors. :type force_poisson: bool :param background: Subtract the background (True/False)? :type background: bool """ file = fits.open(phafile) header = file['SPECTRUM'].header data = file['SPECTRUM'].data self.NumberSpectra = header['NAXIS2'] self.tg_m = data['TG_M'] self.tg_part = data['TG_PART'] self.instrument = header['INSTRUME'] self.telescope = header['TELESCOP'] self.grating = header['GRATING'] for i in np.arange(self.NumberSpectra): pha = Pha() # Read Channel information pha.read_header(header) # Read Channel information pha.Channel = data['CHANNEL'][i] pha.FirstChannel = pha.Channel[0] pha.DetChans = pha.Channel.size # Read the spectrum and convert to rate if necessary if pha.PhaType == 'RATE': pha.Rate = data['RATE'][i] else: pha.Rate = np.zeros(pha.DetChans, dtype=float) for j in np.arange(pha.DetChans): pha.Rate[j] = float(data['COUNTS'][i][j]) / pha.Exposure if force_poisson: poisson = True else: poisson = pha.Poisserr # See if there are Statistical Errors present if not poisson: try: pha.StatError = data['STAT_ERR'][i] except KeyError: pha.StatError = None message.error("No Poisson errors, but no STAT_ERR keyword found.") return 1 else: pha.StatError = np.zeros(pha.DetChans, dtype=float) for j in np.arange(pha.DetChans): pha.StatError[j] = math.sqrt(pha.Rate[j] / pha.Exposure) # Are there systematic errors? try: pha.SysError = data['SYS_ERR'][i] except KeyError: pha.SysError = np.zeros(pha.DetChans, dtype=float) if pha.PhaType == 'RATE': pha.SysError = pha.SysError / pha.Exposure # Are there quality flags? try: pha.Quality = data['QUALITY'][i] except KeyError: pha.Quality = np.zeros(pha.DetChans, dtype=int) # Are there grouping flags? try: pha.Grouping = data['GROUPING'][i] except KeyError: pha.Grouping = np.zeros(pha.DetChans, dtype=int) # Is there a backscale column? try: pha.BackScaling = data['BACKSCAL'][i] except KeyError: pha.BackScaling = np.ones(pha.DetChans, dtype=float) * header['BACKSCAL'] # Is there an areascale column? try: pha.AreaScaling = data['AREASCAL'][i] except KeyError: pha.AreaScaling = np.ones(pha.DetChans, dtype=float) * header['AREASCAL'] if background: pha.Pha2Back = True pha.BackRate = (data['BACKGROUND_UP'][i] + data['BACKGROUND_DOWN'][i]) / pha.Exposure pha.BackStatError = np.zeros(data['BACKGROUND_UP'].size, dtype=float) for j in np.arange(pha.DetChans): pha.BackStatError[j] = math.sqrt(pha.BackRate[j] / pha.Exposure) pha.Pha2BackScal = header['BACKSCUP'] + header['BACKSCDN'] else: pha.Pha2Back = False pha.BackRate = np.zeros(pha.DetChans, dtype=float) pha.BackStatError = np.zeros(pha.DetChans, dtype=float) pha.Pha2BackScal = 1.0 self.phalist.append(pha) file.close() return 0
[docs] def combine_orders(self, grating): """Combine the orders for spectra from the same grating (1 = HETG, 2 = METG, 3 = LETG). :param grating: Grating number to combine the orders for. :type grating: int """ # Select rows to combine tocombine = np.where(self.tg_part == grating)[0] if tocombine.size == 0: message.error("Grating number not found in dataset.") return 1 if tocombine.size == 1: message.error("Only a single order found. No combining will be done.") return 1 # Create new PHA file to output (set first row as default). srcpha = self.phalist[tocombine[0]] bkgpha = Pha() bkgpha.StatError = np.zeros(srcpha.DetChans, dtype=float) for i in np.arange(tocombine.size): if i == 0: continue ipha = self.phalist[tocombine[i]] srcpha.Rate = srcpha.Rate + ipha.Rate bkgpha.Rate = srcpha.BackRate + ipha.BackRate for j in np.arange(srcpha.DetChans): if srcpha.StatError is not None: srcpha.StatError[j] = math.sqrt(srcpha.StatError[j]**2 + ipha.StatError[j]**2) bkgpha.StatError[j] = math.sqrt(srcpha.BackStatError[j]**2 + ipha.BackStatError[j]**2) srcpha.SysError[j] = math.sqrt(srcpha.SysError[j]**2 + ipha.SysError[j]**2) if ipha.Quality[j] != 0: srcpha.Quality = 1 # Remove grouping for now (maybe implemented later) srcpha.Grouping = 0 * srcpha.Grouping srcpha.AreaScaling = srcpha.AreaScaling + ipha.AreaScaling srcpha.BackScaling = srcpha.BackScaling + ipha.BackScaling # Calculate the average AreaScaling and BackScaling (Probably wrong!) srcpha.AreaScaling = srcpha.AreaScaling / tocombine.size srcpha.BackScaling = srcpha.BackScaling / tocombine.size bkgpha.AreaScaling = np.ones(srcpha.DetChans, dtype=float) bkgpha.BackScaling = srcpha.Pha2BackScal * np.ones(srcpha.DetChans, dtype=float) bkgpha.Quality = np.zeros(srcpha.DetChans, dtype=int) bkgpha.Exposure = srcpha.Exposure return srcpha, bkgpha