#!/usr/bin/env python
from . import pyspex_f2py
import os
import numpy
[docs]class Data:
"""Main data class used to set and get observed data.
:ivar ninst: Number of instruments loaded.
:vartype ninst: int
:ivar inst: Python list of instrument objects.
:vartype inst: list of objects
"""
def __init__(self):
self.ninst = 0 # Number of instruments loaded.
self.inst = [] # Python list of instrument objects.
[docs] def update(self):
"""Update the number of instruments."""
self.ninst = int(pyspex_f2py.api_instrument.api_instrument_n())
for i in numpy.arange(self.ninst):
ins = Instrument()
ins.update(i+1)
self.inst.append(ins)
[docs] def load(self, resfile, spofile):
"""Add a new set of spectrum and response file to the dataset. Provide the
full filename including extension!
:param resfile: SPEX response file name (including .res extension).
:type resfile: str
:param spofile: SPEX spectrum file name (including .spo extension).
:type spofile: str
:rtype: int
"""
res = os.path.splitext(resfile)[0]
rext = os.path.splitext(resfile)[1]
if rext != '.res':
print("Error: Response file does not have the proper .res extension.")
return -1
spo = os.path.splitext(spofile)[0]
sext = os.path.splitext(spofile)[1]
if sext != '.spo':
print("Error: Spectrum file does not have the proper .spo extension.")
return -1
cmd = 'data {:s} {:s}'.format(res, spo)
ier = pyspex_f2py.api_command(cmd)
if ier == 0:
self.update()
return ier
[docs] def delete(self, ins):
"""Delete instrument with number `ins`.
:param ins: Instrument number to delete.
:type ins: int
"""
ier = pyspex_f2py.api_command("data delete inst {:d}".format(ins))
if ier == 0:
self.update()
return ier
[docs] def save(self, ins, spofile, overwrite=False):
"""Save a spectrum to a .spo file.
:param ins: Instrument number to save.
:type ins: int
:param spofile: Output filename for SPEX output file (including .spo extension).
:type spofile: str
:param overwrite: (Optional) Overwrite an existing .spo file with the same name.
:type overwrite: bool
"""
spo = os.path.splitext(spofile)[0]
sext = os.path.splitext(spofile)[1]
if sext != '.spo':
print("Error: Spectrum file does not have the proper .spo extension.")
return -1
if overwrite:
ier = pyspex_f2py.api_command("data save {:d} {:s} overwrite".format(ins, spo))
else:
ier = pyspex_f2py.api_command("data save {:d} {:s}".format(ins, spo))
return ier
[docs]class Instrument:
"""Properties of an instrument.
:ivar nsector: Number of sectors in instrument.
:vartype nsector: int
:ivar nregion: Number of data regions in response.
:vartype nsector: int
:ivar nreg: Number of regions in data.
:vartype nreg: int
:ivar ncomp: Number of response components.
:vartype ncomp: int
:ivar index: Index number of this instrument.
:vartype index: int
:ivar sponame: Filename of .spo file.
:vartype sponame: str
:ivar resname: Filename of .res file
:vartype resname: str
:ivar reg: List of regions for this instrument.
:vartype reg: list of objects
"""
def __init__(self):
self.index = 0 # Index number of this instrument.
self.nsector = 0 # Number of sectors in instrument
self.nregion = 0 # Number of data regions in response
self.nreg = 0 # Number of regions in data
self.ncomp = 0 # Number of response components
self.sponame = '' # Filename of .spo file
self.resname = '' # Filename of .res file
self.reg = [] # List of regions for this instrument
[docs] def update(self, iins):
"""Update the instrument information.
:param iins: Instrument number to update.
:type iins: int
"""
self.index = iins
pyspex_f2py.api_instrument.api_instrument_update(float(iins))
self.nsector = int(pyspex_f2py.api_instrument.inst_nsector)
self.nregion = int(pyspex_f2py.api_instrument.inst_nregion)
self.nreg = int(pyspex_f2py.api_instrument.inst_nreg)
self.ncomp = int(pyspex_f2py.api_instrument.inst_ncomp)
self.sponame = pyspex_f2py.api_instrument.inst_sponame.view('S252').tobytes().decode('utf-8')
self.resname = pyspex_f2py.api_instrument.inst_resname.view('S252').tobytes().decode('utf-8')
for i in numpy.arange(self.nreg):
r = Region()
r.update(iins, i+1)
self.reg.append(r)
[docs]class Region:
"""Properties of one region.
:ivar index: Region number.
:vartype index: int
:ivar emin: Min data energy range (keV).
:vartype emin: float
:ivar emax: Max data energy range (keV).
:vartype emax: float
:ivar srccount: Net source counts.
:vartype srccount: float
:ivar srccerr: Net source count error.
:vartype srccerr: float
:ivar bkgcount: Subtracted background counts.
:vartype bkgcount: float
:ivar bkgcerr: Error subtracted background counts.
:vartype bkgcerr: float
:ivar srcrate: Net source count rate (counts/s).
:vartype srcrate: float
:ivar srcrerr: Net source count rate error (counts/s).
:vartype srcrerr: float
:ivar bkgrate: Background count rate subtracted.
:vartype bkgrate: float
:ivar bkgrerr: Error background count rate subtracted.
:vartype bkgrerr: float
:ivar mbkgrate: Model background count rate.
:vartype mbkgrate: float
:ivar tintmin: Minimum integration time per channel.
:vartype tintmin: float
:ivar tintmax: Maximum integration time per channel.
:vartype tintmax: float
:ivar tintaver: Average integration time per channel.
:vartype tintaver: float
"""
def __init__(self):
self.index = 0 # Region number
self.emin = 0. # Min data energy range (keV)
self.emax = 0. # Max data energy range (keV)
self.srccount = 0. # Net source counts
self.srccerr = 0. # Net source count error
self.bkgcount = 0. # Subtracted background counts
self.bkgcerr = 0. # Error subtracted background counts
self.srcrate = 0. # Net source count rate (counts/s)
self.srcrerr = 0. # Net source count rate error (counts/s)
self.bkgrate = 0. # Background count rate subtracted
self.bkgrerr = 0. # Error background count rate subtracted
self.mbkgrate = 0. # Model background count rate
self.tintmin = 0. # Minimum integration time per channel
self.tintmax = 0. # Maximum integration time per channel
self.tintaver = 0. # Average integration time per channel
[docs] def update(self, iins, ireg):
"""Update the region properties.
:param iins: Instrument number.
:type iins: int
:param ireg: Region number to update.
:type ireg: int
"""
self.index = int(ireg)
pyspex_f2py.api_region.api_reg_update(float(iins), float(ireg))
self.emin = float(pyspex_f2py.api_region.reg_edmin)
self.emax = float(pyspex_f2py.api_region.reg_edmax)
self.srccount = float(pyspex_f2py.api_region.reg_sc)
self.srccerr = float(pyspex_f2py.api_region.reg_sce)
self.bkgcount = float(pyspex_f2py.api_region.reg_bc)
self.bkgcerr = float(pyspex_f2py.api_region.reg_dbc)
self.srcrate = float(pyspex_f2py.api_region.reg_scs)
self.srcrerr = float(pyspex_f2py.api_region.reg_scse)
self.bkgrate = float(pyspex_f2py.api_region.reg_bce)
self.bkgrerr = float(pyspex_f2py.api_region.reg_dbce)
self.mbkgrate = float(pyspex_f2py.api_region.reg_bmce)
self.tintmin = float(pyspex_f2py.api_region.reg_tintmin)
self.tintmax = float(pyspex_f2py.api_region.reg_tintmax)
self.tintaver = float(pyspex_f2py.api_region.reg_tintaver)
[docs]class Simulate:
"""Class to simulate spectra."""
def __init__(self):
pass
[docs] def simulate(self, extime, inst=None, reg=None, ssys=None, bsys=None, noise=None, bnoise=None, seed=None):
"""Simulate a spectrum for exposure time extime and optionally with a number of options.
:param extime: Exposure time to simulate.
:type extime: float
:param inst: (Optional) Instrument range to simulate (default all)
:type inst: str
:param reg: (Optional) Region range to simulate (default all)
:type reg: str
:param ssys: (Optional) Add a systematic error to the source spectrum (Default 0).
:type ssys: float
:param bsys: (Optional) Add a systematic error to the background spectrum (Default 0).
:type bsys: float
:param noise: (Optional) Add Poisson noise to the simulated source spectrum (Default True).
:type noise: bool
:param bnoise: (Optional) Add Poisson noise to the simulated background spectrum (Default False).
:type bnoise: bool
:param seed: (Optional) Set the random seed for the simulation (Default: system clock).
:type seed: int
"""
# Instrument range
if inst is not None:
ier = self.set_instrument(inst)
# Region range
if reg is not None:
ier = self.set_region(reg)
# Default values for systematic errors
api_ssys = 0.
api_bsys = 0
if ssys is not None:
api_ssys = ssys
if bsys is not None:
api_bsys = bsys
if ssys is not None or bsys is not None:
ier = self.set_syserr(api_ssys, api_bsys)
# Noise settings
if noise is not None:
ier = self.set_noise(noise)
if bnoise is not None:
ier = self.set_bnoise(bnoise)
if seed is not None:
ier = self.set_random_seed(seed)
else:
ier = self.set_random()
ier = self.simulate_exposure(extime)
return ier
[docs] def set_instrument(self, irange):
"""Define the range of instruments to simulate.
:param irange: Instrument range to simulate (default all)
:type irange: str
"""
ier = pyspex_f2py.api_data_simulate.api_simulate_instrument(irange)
return ier
[docs] def set_region(self, rrange):
"""Define the range of regions to simulate.
:param rrange: Region range to simulate (default all)
:type rrange: str
"""
ier = pyspex_f2py.api_data_simulate.api_simulate_region(rrange)
return ier
[docs] def set_syserr(self, src, bkg):
"""Add a systematic error to the source spectrum (src) and to the background spectrum (bkg).
:param src: Add a systematic error to the source spectrum.
:type src: float
:param bkg: Add a systematic error to the background spectrum.
:type bkg: float
"""
ier = pyspex_f2py.api_data_simulate.api_simulate_syserr(src, bkg)
return ier
[docs] def set_noise(self, status):
"""Add Poisson noise to the source spectrum (status is True or False).
:param status: Add Poisson noise to the simulated source spectrum.
:type status: bool
"""
ier = pyspex_f2py.api_data_simulate.api_simulate_noise(status)
return ier
[docs] def set_bnoise(self, status):
"""Add Poisson noise to the background spectrum (status is True or False).
:param status: Add Poisson noise to the simulated background spectrum.
:type status: bool
"""
ier = pyspex_f2py.api_data_simulate.api_simulate_bnoise(status)
return ier
[docs] def set_random(self):
"""Set the random seed to a random number (default)."""
ier = pyspex_f2py.api_data_simulate.api_simulate_random()
return ier
[docs] def set_random_seed(self, seed):
"""Set the random seed to an integer value.
:param seed: Set the random seed for the simulation.
:type seed: int
"""
ier = pyspex_f2py.api_data_simulate.api_simulate_random_seed(float(seed))
return ier
[docs] def simulate_exposure(self, extime):
"""Simulate the spectrum for the provided exposure time.
:param extime: Exposure time to simulate.
:type extime: float
"""
ier = pyspex_f2py.api_data_simulate.api_simulate_exposure(extime)
return ier
[docs]class Bins:
"""Class containing the binning methods."""
def __init__(self):
pass
[docs] def bin(self, inst, reg, elow, ehigh, factor, unit=None):
"""Bin the spectrum using a fixed factor.
:param inst: Instrument number.
:type inst: int
:param reg: Region number.
:type reg: int
:param elow: Start point of energy/channel interval.
:type elow: float
:param ehigh: End point of energy/channel interval.
:type ehigh: float
:param factor: Binning factor
:type factor: int
:param unit: Unit of the energy/channel range, for example: 'kev', 'ev', 'ryd', 'j', 'hz', 'ang', 'nm'
:type unit: str
"""
ier = pyspex_f2py.api_data_bin.api_bin_bin(float(inst), float(reg), elow, ehigh, float(factor), unit)
return ier
[docs] def obin(self, inst, reg, elow, ehigh, unit=None):
"""Bin the spectrum optimally given the instrument resolution and statistics.
:param inst: Instrument number.
:type inst: int
:param reg: Region number.
:type reg: int
:param elow: Start point of energy/channel interval.
:type elow: float
:param ehigh: End point of energy/channel interval.
:type ehigh: float
:param unit: Unit of the energy/channel range, for example: 'kev', 'ev', 'ryd', 'j', 'hz', 'ang', 'nm'
:type unit: str
"""
ier = pyspex_f2py.api_data_bin.api_bin_obin(float(inst), float(reg), elow, ehigh, unit)
return ier
[docs] def rbin(self, inst, reg, elow, ehigh, unit=None):
"""Bin the spectrum and the response optimally given the instrument resolution and statistics.
:param inst: Instrument number.
:type inst: int
:param reg: Region number.
:type reg: int
:param elow: Start point of energy/channel interval.
:type elow: float
:param ehigh: End point of energy/channel interval.
:type ehigh: float
:param unit: Unit of the energy/channel range, for example: 'kev', 'ev', 'ryd', 'j', 'hz', 'ang', 'nm'
:type unit: str
"""
ier = pyspex_f2py.api_data_bin.api_bin_rbin(float(inst), float(reg), elow, ehigh, unit)
return ier
[docs] def vbin(self, inst, reg, elow, ehigh, factor, snr, unit=None):
"""Bin the spectrum using a variable bin size, given a minimum bin factor and a minimum signal to noise ratio.
:param inst: Instrument number.
:type inst: int
:param reg: Region number.
:type reg: int
:param elow: Start point of energy/channel interval.
:type elow: float
:param ehigh: End point of energy/channel interval.
:type ehigh: float
:param factor: Minimal binning factor
:type factor: int
:param snr: Minimal signal to noise for a data point after binning.
:type snr: float
:param unit: Unit of the energy/channel range, for example: 'kev', 'ev', 'ryd', 'j', 'hz', 'ang', 'nm'
:type unit: str
"""
ier = pyspex_f2py.api_data_bin.api_bin_vbin(float(inst), float(reg), elow, ehigh, float(factor), snr, unit)
return ier
[docs] def syserr(self, inst, reg, elow, ehigh, src, bkg, unit=None):
"""Add an additional error to the source and background spectrum.
:param inst: Instrument number.
:type inst: int
:param reg: Region number.
:type reg: int
:param elow: Start point of energy/channel interval.
:type elow: float
:param ehigh: End point of energy/channel interval.
:type ehigh: float
:param src: Error value to be quadratically added to the current error bar of the source spectrum.
:type src: float
:param bkg: Error value to be quadratically added to the current error bar of the background spectrum.
:type bkg: float
:param unit: Unit of the energy/channel range, for example: 'kev', 'ev', 'ryd', 'j', 'hz', 'ang', 'nm'
:type unit: str
"""
ier = pyspex_f2py.api_data_bin.api_bin_syserr(float(inst), float(reg), elow, ehigh, src, bkg, unit)
return ier
[docs] def use(self, inst, reg, elow, ehigh, unit=None):
"""Use the bins given by the energy/channel range.
:param inst: Instrument number.
:type inst: int
:param reg: Region number.
:type reg: int
:param elow: Start point of energy/channel interval.
:type elow: float
:param ehigh: End point of energy/channel interval.
:type ehigh: float
:param unit: Unit of the energy/channel range, for example: 'kev', 'ev', 'ryd', 'j', 'hz', 'ang', 'nm'
:type unit: str
"""
ier = pyspex_f2py.api_data_bin.api_bin_use(float(inst), float(reg), elow, ehigh, unit)
return ier
[docs] def ignore(self, inst, reg, elow, ehigh, unit=None):
"""Ignore the bins given by the energy/channel range.
:param inst: Instrument number.
:type inst: int
:param reg: Region number.
:type reg: int
:param elow: Start point of energy/channel interval.
:type elow: float
:param ehigh: End point of energy/channel interval.
:type ehigh: float
:param unit: Unit of the energy/channel range, for example: 'kev', 'ev', 'ryd', 'j', 'hz', 'ang', 'nm'
:type unit: str
"""
ier = pyspex_f2py.api_data_bin.api_bin_ignore(float(inst), float(reg), elow, ehigh, unit)
return ier
[docs] def reset(self, inst, reg):
"""Reset the binning and use status to use all with the original binning.
:param inst: Instrument number.
:type inst: int
:param reg: Region number.
:type reg: int
"""
ier = pyspex_f2py.api_data_bin.api_bin_use(float(inst), float(reg), 1, 10000000, unit=None)
ier = pyspex_f2py.api_data_bin.api_bin_bin(float(inst), float(reg), 1, 10000000, 1, unit=None)
return ier