def test_simulate_bkgnd_spectrum(): tmpdir = tempfile.mkdtemp() curdir = os.getcwd() os.chdir(tmpdir) prng = RandomState(29) hdxi_arf = AuxiliaryResponseFile("xrs_hdxi_3x10.arf") hdxi_rmf = RedistributionMatrixFile("xrs_hdxi.rmf") exp_time = 50000.0 fov = 3600.0 simulate_spectrum(None, "hdxi", exp_time, "test_bkgnd.pha", instr_bkgnd=True, foreground=True, prng=prng, overwrite=True, bkgnd_area=(fov, "arcsec**2")) ch_min = hdxi_rmf.e_to_ch(0.7)-hdxi_rmf.cmin ch_max = hdxi_rmf.e_to_ch(2.0)-hdxi_rmf.cmin f = pyfits.open("test_bkgnd.pha") ncts = f["SPECTRUM"].data["COUNTS"][ch_min:ch_max].sum() f.close() S = ncts/exp_time/fov dS = np.sqrt(ncts)/exp_time/fov foreground = ConvolvedBackgroundSpectrum(hm_astro_bkgnd, hdxi_arf) f_sum = foreground.get_flux_in_band(0.7, 2.0)[0] i_sum = acisi_particle_bkgnd.get_flux_in_band(0.7, 2.0)[0] b_sum = (f_sum+i_sum).to("ph/(arcsec**2*s)").value assert np.abs(S-b_sum) < 1.645*dS os.chdir(curdir) shutil.rmtree(tmpdir)
def write_spectrum(evtfile, specfile, overwrite=False): r""" Bin event energies into a spectrum and write it to a FITS binary table. Does not do any grouping of channels, and will automatically determine PI or PHA. Parameters ---------- evtfile : string The name of the event file to read the events from. specfile : string The name of the spectrum file to be written. overwrite : boolean, optional Whether or not to overwrite an existing file with the same name. Default: False """ from soxs.instrument import RedistributionMatrixFile parameters = {} if isinstance(evtfile, str): f = pyfits.open(evtfile) spectype = f["EVENTS"].header["CHANTYPE"] rmf = f["EVENTS"].header["RESPFILE"] p = f["EVENTS"].data[spectype] exp_time = f["EVENTS"].header["EXPOSURE"] for key in ["RESPFILE", "ANCRFILE", "MISSION", "TELESCOP", "INSTRUME"]: parameters[key] = f["EVENTS"].header[key] f.close() else: rmf = evtfile["rmf"] spectype = evtfile["channel_type"] p = evtfile[spectype] parameters["RESPFILE"] = os.path.split(rmf)[-1] parameters["ANCRFILE"] = os.path.split(evtfile["arf"])[-1] parameters["TELESCOP"] = evtfile["telescope"] parameters["INSTRUME"] = evtfile["instrument"] parameters["MISSION"] = evtfile["mission"] exp_time = evtfile["exposure_time"] rmf = RedistributionMatrixFile(rmf) minlength = rmf.n_ch if rmf.cmin == 1: minlength += 1 spec = np.bincount(p, minlength=minlength) if rmf.cmin == 1: spec = spec[1:] bins = (np.arange(rmf.n_ch) + rmf.cmin).astype("int32") _write_spectrum(bins, spec, exp_time, spectype, parameters, specfile, overwrite=overwrite)
def test_simulate_bkgnd_spectrum(): tmpdir = tempfile.mkdtemp() curdir = os.getcwd() os.chdir(tmpdir) prng = RandomState(29) hdxi_arf = AuxiliaryResponseFile("xrs_hdxi_3x10.arf") hdxi_rmf = RedistributionMatrixFile("xrs_hdxi.rmf") exp_time = 50000.0 fov = 3600.0 simulate_spectrum(None, "hdxi", exp_time, "test_bkgnd.pha", instr_bkgnd=True, foreground=True, prng=prng, overwrite=True, bkgnd_area=(fov, "arcsec**2")) ch_min = hdxi_rmf.e_to_ch(0.7) - hdxi_rmf.cmin ch_max = hdxi_rmf.e_to_ch(2.0) - hdxi_rmf.cmin f = pyfits.open("test_bkgnd.pha") ncts = f["SPECTRUM"].data["COUNTS"][ch_min:ch_max].sum() f.close() S = ncts / exp_time / fov dS = np.sqrt(ncts) / exp_time / fov foreground = ConvolvedBackgroundSpectrum(hm_astro_bkgnd, hdxi_arf) f_sum = foreground.get_flux_in_band(0.7, 2.0)[0] i_sum = acisi_particle_bkgnd.get_flux_in_band(0.7, 2.0)[0] b_sum = (f_sum + i_sum).to("ph/(arcsec**2*s)").value assert np.abs(S - b_sum) < 1.645 * dS os.chdir(curdir) shutil.rmtree(tmpdir)
from soxs.spectra import ApecGenerator from soxs.spatial import PointSourceModel from soxs.simput import SimputCatalog from soxs.instrument_registry import \ get_instrument_from_registry from soxs.instrument import instrument_simulator, \ RedistributionMatrixFile, simulate_spectrum from soxs.events import write_spectrum from numpy.random import RandomState from numpy.testing import assert_allclose from soxs.tests.utils import spectrum_answer_testing, \ file_answer_testing inst_name = "lynx_lxm" rmf = RedistributionMatrixFile.from_instrument(inst_name) agen0 = ApecGenerator(0.01, 10.0, 20000, broadening=True) agen_var0 = ApecGenerator(0.01, 10.0, 20000, var_elem=["O", "Fe"], broadening=True) agen_nolines0 = ApecGenerator(0.01, 10.0, 20000, broadening=True, nolines=True) agen_aspl0 = ApecGenerator(0.01, 10.0, 20000, broadening=True, abund_table="aspl") agen = ApecGenerator(rmf.elo[0], rmf.ehi[-1], rmf.n_e, broadening=True) agen_var = ApecGenerator(rmf.elo[0], rmf.ehi[-1], rmf.n_e, var_elem=["O", "Fe"], broadening=True) agen_nolines = ApecGenerator(rmf.elo[0], rmf.ehi[-1], rmf.n_e, broadening=True, nolines=True) agen_aspl = ApecGenerator(rmf.elo[0], rmf.ehi[-1], rmf.n_e, broadening=True, abund_table="aspl") agen_nei = ApecGenerator(rmf.elo[0], rmf.ehi[-1], rmf.n_e,
AuxiliaryResponseFile, instrument_simulator from soxs.events import write_spectrum from soxs.instrument_registry import get_instrument_from_registry ckms = clight.in_units("km/s").v def setup(): from yt.config import ytcfg ytcfg["yt", "__withintesting"] = "True" try: mucal_spec = get_instrument_from_registry("mucal") except KeyError: pass rmf = RedistributionMatrixFile(mucal_spec["rmf"]) arf = AuxiliaryResponseFile(mucal_spec['arf']) fit_model = TableApecModel(rmf.elo[0], rmf.ehi[-1], rmf.n_e) agen_var = TableApecModel(rmf.elo[0], rmf.ehi[-1], rmf.n_e, var_elem=["O", "Ca"], thermal_broad=True) def mymodel(pars, x, xhi=None): dx = x[1]-x[0] tm = TBabsModel(pars[0]) tbabs = tm.get_absorb(x+0.5*dx) bapec = fit_model.return_spectrum(pars[1], pars[2], pars[3], pars[4], velocity=pars[5]) eidxs = np.logical_and(rmf.elo >= x[0]-0.5*dx, rmf.elo <= x[-1]+0.5*dx) return tbabs*bapec[eidxs]
def plaw_fit(alpha_sim): tmpdir = tempfile.mkdtemp() curdir = os.getcwd() os.chdir(tmpdir) nH_sim = 0.02 norm_sim = 1.0e-4 redshift = 0.01 exp_time = 5.0e4 area = 40000.0 inst_name = "hdxi" spec = Spectrum.from_powerlaw(alpha_sim, redshift, norm_sim) spec.apply_foreground_absorption(nH_sim) e = spec.generate_energies(exp_time, area) pt_src = PointSourceModel(30.0, 45.0, e.size) write_photon_list("plaw_model", "plaw_model", e.flux, pt_src.ra, pt_src.dec, e, clobber=True) instrument_simulator("plaw_model_simput.fits", "plaw_model_evt.fits", exp_time, inst_name, [30.0, 45.0], astro_bkgnd=None, instr_bkgnd_scale=0.0) inst = get_instrument_from_registry(inst_name) arf = AuxiliaryResponseFile(inst["arf"]) rmf = RedistributionMatrixFile(inst["rmf"]) os.system("cp %s ." % arf.filename) os.system("cp %s ." % rmf.filename) write_spectrum("plaw_model_evt.fits", "plaw_model_evt.pha", clobber=True) load_user_model(mymodel, "wplaw") add_user_pars("wplaw", ["nH", "norm", "redshift", "alpha"], [0.01, norm_sim * 0.8, redshift, 0.9], parmins=[0.0, 0.0, 0.0, 0.1], parmaxs=[10.0, 1.0e9, 10.0, 10.0], parfrozen=[False, False, True, False]) load_pha("plaw_model_evt.pha") set_stat("cstat") set_method("simplex") ignore(":0.5, 9.0:") set_model("wplaw") fit() set_covar_opt("sigma", 1.645) covar() res = get_covar_results() assert np.abs(res.parvals[0] - nH_sim) < res.parmaxes[0] assert np.abs(res.parvals[1] - norm_sim) < res.parmaxes[1] assert np.abs(res.parvals[2] - alpha_sim) < res.parmaxes[2] os.chdir(curdir) shutil.rmtree(tmpdir)
def write_spectrum(evtfile, specfile, clobber=False): r""" Bin event energies into a spectrum and write it to a FITS binary table. Can bin on energy or channel. In the latter case, the spectral binning will be determined by the RMF binning. Parameters ---------- specfile : string The name of the FITS file to be written. bin_type : string, optional Bin on "energy" or "channel". If an RMF is detected, channel information will be imported from it. emin : float, optional The minimum energy of the spectral bins in keV. Only used if binning without an RMF. emax : float, optional The maximum energy of the spectral bins in keV. Only used if binning without an RMF. nchan : integer, optional The number of channels. Only used if binning without an RMF. """ f = pyfits.open(evtfile) spectype = f["EVENTS"].header["CHANTYPE"] rmf = RedistributionMatrixFile(f["EVENTS"].header["RESPFILE"]) minlength = rmf.n_ch if rmf.cmin == 1: minlength += 1 spec = np.bincount(f["EVENTS"].data[spectype], minlength=minlength) if rmf.cmin == 1: spec = spec[1:] bins = (np.arange(rmf.n_ch) + rmf.cmin).astype("int32") col1 = pyfits.Column(name='CHANNEL', format='1J', array=bins) col2 = pyfits.Column(name=spectype.upper(), format='1D', array=bins.astype("float64")) col3 = pyfits.Column(name='COUNTS', format='1J', array=spec.astype("int32")) col4 = pyfits.Column(name='COUNT_RATE', format='1D', array=spec / f["EVENTS"].header["EXPOSURE"]) coldefs = pyfits.ColDefs([col1, col2, col3, col4]) tbhdu = pyfits.BinTableHDU.from_columns(coldefs) tbhdu.update_ext_name("SPECTRUM") tbhdu.header["DETCHANS"] = spec.size tbhdu.header["TOTCTS"] = spec.sum() tbhdu.header["EXPOSURE"] = f["EVENTS"].header["EXPOSURE"] tbhdu.header["LIVETIME"] = f["EVENTS"].header["EXPOSURE"] tbhdu.header["CONTENT"] = spectype tbhdu.header["HDUCLASS"] = "OGIP" tbhdu.header["HDUCLAS1"] = "SPECTRUM" tbhdu.header["HDUCLAS2"] = "TOTAL" tbhdu.header["HDUCLAS3"] = "TYPE:I" tbhdu.header["HDUCLAS4"] = "COUNT" tbhdu.header["HDUVERS"] = "1.1.0" tbhdu.header["HDUVERS1"] = "1.1.0" tbhdu.header["CHANTYPE"] = spectype tbhdu.header["BACKFILE"] = "none" tbhdu.header["CORRFILE"] = "none" tbhdu.header["POISSERR"] = True for key in ["RESPFILE", "ANCRFILE", "MISSION", "TELESCOP", "INSTRUME"]: tbhdu.header[key] = f["EVENTS"].header[key] tbhdu.header["AREASCAL"] = 1.0 tbhdu.header["CORRSCAL"] = 0.0 tbhdu.header["BACKSCAL"] = 1.0 f.close() hdulist = pyfits.HDUList([pyfits.PrimaryHDU(), tbhdu]) hdulist.writeto(specfile, clobber=clobber)
make_simple_instrument def setup(): from yt.config import ytcfg ytcfg["yt", "__withintesting"] = "True" try: make_simple_instrument("acisi_cy19", "sq_acisi_cy19", 20.0, 2400) except KeyError: pass acis_spec = get_instrument_from_registry("sq_acisi_cy19") rmf = RedistributionMatrixFile(acis_spec["rmf"]) arf = AuxiliaryResponseFile(acis_spec['arf']) def mymodel(pars, x, xhi=None): dx = x[1] - x[0] xmid = x + 0.5 * dx wm = WabsModel(pars[0]) wabs = wm.get_absorb(xmid) plaw = pars[1] * dx * (xmid * (1.0 + pars[2]))**(-pars[3]) return wabs * plaw @requires_module("sherpa") def test_power_law(): plaw_fit(1.1, prng=29)
def write_fits_file(self, fitsfile, fov, nx, overwrite=False): """ Write events to a FITS binary table file. The result is a standard "event file" which can be processed by standard X-ray analysis tools. Parameters ---------- fitsfile : string The name of the event file to write. fov : float, (value, unit) tuple, :class:`~yt.units.yt_array.YTQuantity`, or :class:`~astropy.units.Quantity` The field of view of the event file. If units are not provided, they are assumed to be in arcminutes. nx : integer The resolution of the image (number of pixels on a side). overwrite : boolean, optional Set to True to overwrite a previous file. """ from astropy.time import Time, TimeDelta events = communicate_events(self.events) fov = parse_value(fov, "arcmin") if comm.rank == 0: exp_time = float(self.parameters["exp_time"]) t_begin = Time.now() dt = TimeDelta(exp_time, format='sec') t_end = t_begin + dt dtheta = fov.to("deg").v / nx wcs = pywcs.WCS(naxis=2) wcs.wcs.crpix = [0.5 * (nx + 1)] * 2 wcs.wcs.crval = self.parameters["sky_center"].d wcs.wcs.cdelt = [-dtheta, dtheta] wcs.wcs.ctype = ["RA---TAN", "DEC--TAN"] wcs.wcs.cunit = ["deg"] * 2 xx, yy = wcs.wcs_world2pix(self["xsky"].d, self["ysky"].d, 1) keepx = np.logical_and(xx >= 0.5, xx <= float(nx) + 0.5) keepy = np.logical_and(yy >= 0.5, yy <= float(nx) + 0.5) keep = np.logical_and(keepx, keepy) n_events = keep.sum() mylog.info("Threw out %d events because " % (xx.size - n_events) + "they fell outside the field of view.") col_e = pyfits.Column(name='ENERGY', format='E', unit='eV', array=events["eobs"].in_units("eV").d[keep]) col_x = pyfits.Column(name='X', format='D', unit='pixel', array=xx[keep]) col_y = pyfits.Column(name='Y', format='D', unit='pixel', array=yy[keep]) cols = [col_e, col_x, col_y] if "channel_type" in self.parameters: chantype = self.parameters["channel_type"] if chantype == "pha": cunit = "adu" elif chantype == "pi": cunit = "Chan" col_ch = pyfits.Column(name=chantype.upper(), format='1J', unit=cunit, array=events[chantype][keep]) cols.append(col_ch) time = np.random.uniform(size=n_events, low=0.0, high=float( self.parameters["exp_time"])) col_t = pyfits.Column(name="TIME", format='1D', unit='s', array=time) cols.append(col_t) coldefs = pyfits.ColDefs(cols) tbhdu = pyfits.BinTableHDU.from_columns(coldefs) tbhdu.name = "EVENTS" tbhdu.header["MTYPE1"] = "sky" tbhdu.header["MFORM1"] = "x,y" tbhdu.header["MTYPE2"] = "EQPOS" tbhdu.header["MFORM2"] = "RA,DEC" tbhdu.header["TCTYP2"] = "RA---TAN" tbhdu.header["TCTYP3"] = "DEC--TAN" tbhdu.header["TCRVL2"] = float(self.parameters["sky_center"][0]) tbhdu.header["TCRVL3"] = float(self.parameters["sky_center"][1]) tbhdu.header["TCDLT2"] = -dtheta tbhdu.header["TCDLT3"] = dtheta tbhdu.header["TCRPX2"] = 0.5 * (nx + 1) tbhdu.header["TCRPX3"] = 0.5 * (nx + 1) tbhdu.header["TLMIN2"] = 0.5 tbhdu.header["TLMIN3"] = 0.5 tbhdu.header["TLMAX2"] = float(nx) + 0.5 tbhdu.header["TLMAX3"] = float(nx) + 0.5 if "channel_type" in self.parameters: rmf = RedistributionMatrixFile(self.parameters["rmf"]) tbhdu.header["TLMIN4"] = rmf.cmin tbhdu.header["TLMAX4"] = rmf.cmax tbhdu.header["RESPFILE"] = os.path.split( self.parameters["rmf"])[-1] tbhdu.header["PHA_BINS"] = rmf.n_ch tbhdu.header["ANCRFILE"] = os.path.split( self.parameters["arf"])[-1] tbhdu.header["CHANTYPE"] = self.parameters["channel_type"] tbhdu.header["MISSION"] = self.parameters["mission"] tbhdu.header["TELESCOP"] = self.parameters["telescope"] tbhdu.header["INSTRUME"] = self.parameters["instrument"] tbhdu.header["EXPOSURE"] = exp_time tbhdu.header["TSTART"] = 0.0 tbhdu.header["TSTOP"] = exp_time tbhdu.header["AREA"] = float(self.parameters["area"]) tbhdu.header["HDUVERS"] = "1.1.0" tbhdu.header["RADECSYS"] = "FK5" tbhdu.header["EQUINOX"] = 2000.0 tbhdu.header["HDUCLASS"] = "OGIP" tbhdu.header["HDUCLAS1"] = "EVENTS" tbhdu.header["HDUCLAS2"] = "ACCEPTED" tbhdu.header["DATE"] = t_begin.tt.isot tbhdu.header["DATE-OBS"] = t_begin.tt.isot tbhdu.header["DATE-END"] = t_end.tt.isot hdulist = [pyfits.PrimaryHDU(), tbhdu] if "channel_type" in self.parameters: start = pyfits.Column(name='START', format='1D', unit='s', array=np.array([0.0])) stop = pyfits.Column(name='STOP', format='1D', unit='s', array=np.array([exp_time])) tbhdu_gti = pyfits.BinTableHDU.from_columns([start, stop]) tbhdu_gti.name = "STDGTI" tbhdu_gti.header["TSTART"] = 0.0 tbhdu_gti.header["TSTOP"] = exp_time tbhdu_gti.header["HDUCLASS"] = "OGIP" tbhdu_gti.header["HDUCLAS1"] = "GTI" tbhdu_gti.header["HDUCLAS2"] = "STANDARD" tbhdu_gti.header["RADECSYS"] = "FK5" tbhdu_gti.header["EQUINOX"] = 2000.0 tbhdu_gti.header["DATE"] = t_begin.tt.isot tbhdu_gti.header["DATE-OBS"] = t_begin.tt.isot tbhdu_gti.header["DATE-END"] = t_end.tt.isot hdulist.append(tbhdu_gti) pyfits.HDUList(hdulist).writeto(fitsfile, overwrite=overwrite) comm.barrier()
def make_background(exp_time, instrument, sky_center, foreground=True, ptsrc_bkgnd=True, instr_bkgnd=True, no_dither=False, dither_params=None, roll_angle=0.0, subpixel_res=False, input_sources=None, absorb_model="wabs", nH=0.05, prng=None): """ Make background events. Parameters ---------- exp_time : float, (value, unit) tuple, or :class:`~astropy.units.Quantity` The exposure time to use, in seconds. instrument : string The name of the instrument to use, which picks an instrument specification from the instrument registry. sky_center : array, tuple, or list The center RA, Dec coordinates of the observation, in degrees. foreground : boolean, optional Whether or not to include the Galactic foreground. Default: True instr_bkgnd : boolean, optional Whether or not to include the instrumental background. Default: True no_dither : boolean, optional If True, turn off dithering entirely. Default: False dither_params : array-like of floats, optional The parameters to use to control the size and period of the dither pattern. The first two numbers are the dither amplitude in x and y detector coordinates in arcseconds, and the second two numbers are the dither period in x and y detector coordinates in seconds. Default: [8.0, 8.0, 1000.0, 707.0]. ptsrc_bkgnd : boolean, optional Whether or not to include the point-source background. Default: True Default: 0.05 roll_angle : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`, optional The roll angle of the observation in degrees. Default: 0.0 subpixel_res: boolean, optional If True, event positions are not randomized within the pixels within which they are detected. Default: False input_sources : string, optional If set to a filename, input the point source positions, fluxes, and spectral indices from an ASCII table instead of generating them. Default: None absorb_model : string, optional The absorption model to use, "wabs" or "tbabs". Default: "wabs" nH : float, optional The hydrogen column in units of 10**22 atoms/cm**2. Default: 0.05 prng : :class:`~numpy.random.RandomState` object, integer, or None A pseudo-random number generator. Typically will only be specified if you have a reason to generate the same set of random numbers, such as for a test. Default is None, which sets the seed based on the system time. """ from soxs.background import make_instrument_background, \ make_foreground, make_ptsrc_background prng = parse_prng(prng) exp_time = parse_value(exp_time, "s") roll_angle = parse_value(roll_angle, "deg") try: instrument_spec = instrument_registry[instrument] except KeyError: raise KeyError("Instrument %s is not in the instrument registry!" % instrument) if not instrument_spec["imaging"]: raise RuntimeError("Instrument '%s' is not " % instrument_spec["name"] + "designed for imaging observations!") fov = instrument_spec["fov"] input_events = defaultdict(list) arf_file = get_response_path(instrument_spec["arf"]) arf = AuxiliaryResponseFile(arf_file) rmf_file = get_response_path(instrument_spec["rmf"]) rmf = RedistributionMatrixFile(rmf_file) if ptsrc_bkgnd: mylog.info("Adding in point-source background.") ptsrc_events = make_ptsrc_background(exp_time, fov, sky_center, area=1.2*arf.max_area, input_sources=input_sources, absorb_model=absorb_model, nH=nH, prng=prng) for key in ["ra", "dec", "energy"]: input_events[key].append(ptsrc_events[key]) input_events["flux"].append(ptsrc_events["flux"]) input_events["emin"].append(ptsrc_events["energy"].min()) input_events["emax"].append(ptsrc_events["energy"].max()) input_events["sources"].append("ptsrc_bkgnd") events, event_params = generate_events(input_events, exp_time, instrument, sky_center, no_dither=no_dither, dither_params=dither_params, roll_angle=roll_angle, subpixel_res=subpixel_res, prng=prng) mylog.info("Generated %d photons from the point-source background." % len(events["energy"])) else: nx = instrument_spec["num_pixels"] events = defaultdict(list) if not instrument_spec["dither"]: dither_on = False else: dither_on = not no_dither if dither_params is None: dither_params = [8.0, 8.0, 1000.0, 707.0] dither_dict = {"x_amp": dither_params[0], "y_amp": dither_params[1], "x_period": dither_params[2], "y_period": dither_params[3], "dither_on": dither_on, "plate_scale": instrument_spec["fov"]/nx*60.0} event_params = {"exposure_time": exp_time, "fov": instrument_spec["fov"], "num_pixels": nx, "pix_center": np.array([0.5*(2*nx+1)]*2), "channel_type": rmf.header["CHANTYPE"], "sky_center": sky_center, "dither_params": dither_dict, "plate_scale": instrument_spec["fov"]/nx/60.0, "chan_lim": [rmf.cmin, rmf.cmax], "rmf": rmf_file, "arf": arf_file, "telescope": rmf.header["TELESCOP"], "instrument": instrument_spec['name'], "mission": rmf.header.get("MISSION", ""), "nchan": rmf.n_ch, "roll_angle": roll_angle, "aimpt_coords": instrument_spec["aimpt_coords"]} if "chips" not in event_params: event_params["chips"] = instrument_spec["chips"] if foreground: mylog.info("Adding in astrophysical foreground.") bkg_events = make_foreground(event_params, arf, rmf, prng=prng) for key in bkg_events: events[key] = np.concatenate([events[key], bkg_events[key]]) if instr_bkgnd and instrument_spec["bkgnd"] is not None: mylog.info("Adding in instrumental background.") bkg_events = make_instrument_background(instrument_spec["bkgnd"], event_params, rmf, prng=prng) for key in bkg_events: events[key] = np.concatenate([events[key], bkg_events[key]]) return events, event_params
def generate_events(input_events, exp_time, instrument, sky_center, no_dither=False, dither_params=None, roll_angle=0.0, subpixel_res=False, prng=None): """ Take unconvolved events and convolve them with instrumental responses. This function does the following: 1. Determines which events are observed using the ARF 2. Pixelizes the events, applying PSF effects and dithering 3. Determines energy channels using the RMF This function is not meant to be called by the end-user but is used by the :func:`~soxs.instrument.instrument_simulator` function. Parameters ---------- input_events : string, dict, or None The unconvolved events to be used as input. Can be one of the following: 1. The name of a SIMPUT catalog file. 2. A Python dictionary containing the following items: "ra": A NumPy array of right ascension values in degrees. "dec": A NumPy array of declination values in degrees. "energy": A NumPy array of energy values in keV. "flux": The flux of the entire source, in units of erg/cm**2/s. out_file : string The name of the event file to be written. exp_time : float, (value, unit) tuple, or :class:`~astropy.units.Quantity` The exposure time to use, in seconds. instrument : string The name of the instrument to use, which picks an instrument specification from the instrument registry. sky_center : array, tuple, or list The center RA, Dec coordinates of the observation, in degrees. no_dither : boolean, optional If True, turn off dithering entirely. Default: False dither_params : array-like of floats, optional The parameters to use to control the size and period of the dither pattern. The first two numbers are the dither amplitude in x and y detector coordinates in arcseconds, and the second two numbers are the dither period in x and y detector coordinates in seconds. Default: [8.0, 8.0, 1000.0, 707.0]. roll_angle : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`, optional The roll angle of the observation in degrees. Default: 0.0 subpixel_res: boolean, optional If True, event positions are not randomized within the pixels within which they are detected. Default: False prng : :class:`~numpy.random.RandomState` object, integer, or None A pseudo-random number generator. Typically will only be specified if you have a reason to generate the same set of random numbers, such as for a test. Default is None, which sets the seed based on the system time. """ import pyregion._region_filter as rfilter exp_time = parse_value(exp_time, "s") roll_angle = parse_value(roll_angle, "deg") prng = parse_prng(prng) if isinstance(input_events, dict): parameters = {} for key in ["flux", "emin", "emax", "sources"]: parameters[key] = input_events[key] event_list = [] for i in range(len(parameters["flux"])): edict = {} for key in ["ra", "dec", "energy"]: edict[key] = input_events[key][i] event_list.append(edict) elif isinstance(input_events, string_types): # Assume this is a SIMPUT catalog event_list, parameters = read_simput_catalog(input_events) try: instrument_spec = instrument_registry[instrument] except KeyError: raise KeyError("Instrument %s is not in the instrument registry!" % instrument) if not instrument_spec["imaging"]: raise RuntimeError("Instrument '%s' is not " % instrument_spec["name"] + "designed for imaging observations!") arf_file = get_response_path(instrument_spec["arf"]) rmf_file = get_response_path(instrument_spec["rmf"]) arf = AuxiliaryResponseFile(arf_file) rmf = RedistributionMatrixFile(rmf_file) nx = instrument_spec["num_pixels"] plate_scale = instrument_spec["fov"]/nx/60. # arcmin to deg plate_scale_arcsec = plate_scale * 3600.0 if not instrument_spec["dither"]: dither_on = False else: dither_on = not no_dither if dither_params is None: dither_params = [8.0, 8.0, 1000.0, 707.0] dither_dict = {"x_amp": dither_params[0], "y_amp": dither_params[1], "x_period": dither_params[2], "y_period": dither_params[3], "dither_on": dither_on, "plate_scale": plate_scale_arcsec} event_params = {} event_params["exposure_time"] = exp_time event_params["arf"] = arf.filename event_params["sky_center"] = sky_center event_params["pix_center"] = np.array([0.5*(2*nx+1)]*2) event_params["num_pixels"] = nx event_params["plate_scale"] = plate_scale event_params["rmf"] = rmf.filename event_params["channel_type"] = rmf.header["CHANTYPE"] event_params["telescope"] = rmf.header["TELESCOP"] event_params["instrument"] = instrument_spec['name'] event_params["mission"] = rmf.header.get("MISSION", "") event_params["nchan"] = rmf.n_ch event_params["roll_angle"] = roll_angle event_params["fov"] = instrument_spec["fov"] event_params["chan_lim"] = [rmf.cmin, rmf.cmax] event_params["chips"] = instrument_spec["chips"] event_params["dither_params"] = dither_dict event_params["aimpt_coords"] = instrument_spec["aimpt_coords"] w = pywcs.WCS(naxis=2) w.wcs.crval = event_params["sky_center"] w.wcs.crpix = event_params["pix_center"] w.wcs.cdelt = [-plate_scale, plate_scale] w.wcs.ctype = ["RA---TAN","DEC--TAN"] w.wcs.cunit = ["deg"]*2 rot_mat = get_rot_mat(roll_angle) all_events = defaultdict(list) for i, evts in enumerate(event_list): mylog.info("Detecting events from source %s." % parameters["sources"][i]) # Step 1: Use ARF to determine which photons are observed mylog.info("Applying energy-dependent effective area from %s." % os.path.split(arf.filename)[-1]) refband = [parameters["emin"][i], parameters["emax"][i]] events = arf.detect_events(evts, exp_time, parameters["flux"][i], refband, prng=prng) n_evt = events["energy"].size if n_evt == 0: mylog.warning("No events were observed for this source!!!") else: # Step 2: Assign pixel coordinates to events. Apply dithering and # PSF. Clip events that don't fall within the detection region. mylog.info("Pixeling events.") # Convert RA, Dec to pixel coordinates xpix, ypix = w.wcs_world2pix(events["ra"], events["dec"], 1) xpix -= event_params["pix_center"][0] ypix -= event_params["pix_center"][1] events.pop("ra") events.pop("dec") n_evt = xpix.size # Rotate physical coordinates to detector coordinates det = np.dot(rot_mat, np.array([xpix, ypix])) detx = det[0,:] + event_params["aimpt_coords"][0] dety = det[1,:] + event_params["aimpt_coords"][1] # Add times to events events['time'] = prng.uniform(size=n_evt, low=0.0, high=event_params["exposure_time"]) # Apply dithering x_offset, y_offset = perform_dither(events["time"], dither_dict) detx -= x_offset dety -= y_offset # PSF scattering of detector coordinates if instrument_spec["psf"] is not None: psf_type, psf_spec = instrument_spec["psf"] if psf_type == "gaussian": sigma = psf_spec/sigma_to_fwhm/plate_scale_arcsec detx += prng.normal(loc=0.0, scale=sigma, size=n_evt) dety += prng.normal(loc=0.0, scale=sigma, size=n_evt) else: raise NotImplementedError("PSF type %s not implemented!" % psf_type) # Convert detector coordinates to chip coordinates. # Throw out events that don't fall on any chip. cx = np.trunc(detx)+0.5*np.sign(detx) cy = np.trunc(dety)+0.5*np.sign(dety) if event_params["chips"] is None: events["chip_id"] = np.zeros(n_evt, dtype='int') keepx = np.logical_and(cx >= -0.5*nx, cx <= 0.5*nx) keepy = np.logical_and(cy >= -0.5*nx, cy <= 0.5*nx) keep = np.logical_and(keepx, keepy) else: events["chip_id"] = -np.ones(n_evt, dtype='int') for i, chip in enumerate(event_params["chips"]): thisc = np.ones(n_evt, dtype='bool') rtype = chip[0] args = chip[1:] r = getattr(rfilter, rtype)(*args) inside = r.inside(cx, cy) thisc = np.logical_and(thisc, inside) events["chip_id"][thisc] = i keep = events["chip_id"] > -1 mylog.info("%d events were rejected because " % (n_evt-keep.sum()) + "they do not fall on any CCD.") n_evt = keep.sum() if n_evt == 0: mylog.warning("No events are within the field of view for this source!!!") else: # Keep only those events which fall on a chip for key in events: events[key] = events[key][keep] # Convert chip coordinates back to detector coordinates, unless the # user has specified that they want subpixel resolution if subpixel_res: events["detx"] = detx[keep] events["dety"] = dety[keep] else: events["detx"] = cx[keep] + prng.uniform(low=-0.5, high=0.5, size=n_evt) events["dety"] = cy[keep] + prng.uniform(low=-0.5, high=0.5, size=n_evt) # Convert detector coordinates back to pixel coordinates by # adding the dither offsets back in and applying the rotation # matrix again det = np.array([events["detx"] + x_offset[keep] - event_params["aimpt_coords"][0], events["dety"] + y_offset[keep] - event_params["aimpt_coords"][1]]) pix = np.dot(rot_mat.T, det) events["xpix"] = pix[0,:] + event_params['pix_center'][0] events["ypix"] = pix[1,:] + event_params['pix_center'][1] if n_evt > 0: for key in events: all_events[key] = np.concatenate([all_events[key], events[key]]) if len(all_events["energy"]) == 0: mylog.warning("No events from any of the sources in the catalog were detected!") for key in ["xpix", "ypix", "detx", "dety", "time", "chip_id", event_params["channel_type"]]: all_events[key] = np.array([]) else: # Step 4: Scatter energies with RMF mylog.info("Scattering energies with RMF %s." % os.path.split(rmf.filename)[-1]) all_events = rmf.scatter_energies(all_events, prng=prng) return all_events, event_params
def rmf(self): if self._rmf is None: self._rmf = RedistributionMatrixFile(self.rmf_file) return self._rmf
def plot_spectrum(specfile, plot_energy=True, lw=2, xmin=None, xmax=None, ymin=None, ymax=None, xscale=None, yscale=None, label=None, fontsize=18, fig=None, ax=None, plot_counts=False, **kwargs): """ Make a quick Matplotlib plot of a convolved spectrum from a file. A Matplotlib figure and axis is returned. Parameters ---------- specfile : string The file to be opened for plotting. figsize : tuple of integers, optional The size of the figure on both sides in inches. Default: (10,10) plot_energy : boolean, optional Whether to plot in energy or channel space. Default is to plot in energy, unless the RMF for the spectrum cannot be found. lw : float, optional The width of the lines in the plots. Default: 2.0 px. xmin : float, optional The left-most energy (in keV) or channel to plot. Default is the minimum value in the spectrum. xmax : float, optional The right-most energy (in keV) or channel to plot. Default is the maximum value in the spectrum. ymin : float, optional The lower extent of the y-axis. By default it is set automatically. ymax : float, optional The upper extent of the y-axis. By default it is set automatically. xscale : string, optional The scaling of the x-axis of the plot. Default: "log" yscale : string, optional The scaling of the y-axis of the plot. Default: "log" label : string, optional The label of the spectrum. Default: None fontsize : int Font size for labels and axes. Default: 18 fig : :class:`~matplotlib.figure.Figure`, optional A Figure instance to plot in. Default: None, one will be created if not provided. ax : :class:`~matplotlib.axes.Axes`, optional An Axes instance to plot in. Default: None, one will be created if not provided. plot_counts : boolean, optional If set to True, the counts instead of the count rate will be plotted. Default: False Returns ------- A tuple of the :class:`~matplotlib.figure.Figure` and the :class:`~matplotlib.axes.Axes` objects. """ import matplotlib.pyplot as plt from soxs.instrument import RedistributionMatrixFile f = pyfits.open(specfile) hdu = f["SPECTRUM"] chantype = hdu.header["CHANTYPE"] rmf = hdu.header.get("RESPFILE", None) xerr = None if plot_energy: if rmf is not None: rmf = RedistributionMatrixFile(rmf) x = 0.5 * (rmf.ebounds_data["E_MIN"] + rmf.ebounds_data["E_MAX"]) xerr = 0.5 * (rmf.ebounds_data["E_MAX"] - rmf.ebounds_data["E_MIN"]) xlabel = "Energy (keV)" else: raise RuntimeError("Cannot find the RMF associated with this " "spectrum, so I cannot plot in energy!") else: x = hdu.data[chantype] xlabel = "Channel (%s)" % chantype if plot_counts: y = hdu.data["COUNTS"].astype("float64") yerr = np.sqrt(y) else: if "COUNT_RATE" in hdu.columns.names: y = hdu.data["COUNT_RATE"] else: y = hdu.data["COUNTS"] / hdu.header["EXPOSURE"] yerr = np.sqrt(hdu.data["COUNTS"]) / hdu.header["EXPOSURE"] if plot_energy: yunit = "keV" y /= 2.0 * xerr yerr /= 2.0 * xerr else: yunit = "bin" f.close() if fig is None: fig = plt.figure(figsize=(10, 10)) if xscale is None: if ax is None: xscale = "log" else: xscale = ax.get_xscale() if yscale is None: if ax is None: yscale = "log" else: yscale = ax.get_yscale() if ax is None: ax = fig.add_subplot(111) ax.errorbar(x, y, yerr=yerr, xerr=xerr, lw=lw, label=label, **kwargs) ax.set_xscale(xscale) ax.set_yscale(yscale) ax.set_xlim(xmin, xmax) ax.set_ylim(ymin, ymax) ax.set_xlabel(xlabel, fontsize=fontsize) if plot_counts: ylabel = "Counts (counts/%s)" else: ylabel = "Count Rate (counts/s/%s)" ax.set_ylabel(ylabel % yunit, fontsize=fontsize) ax.tick_params(axis='both', labelsize=fontsize) return fig, ax
def simulate_spectrum(spec, instrument, exp_time, out_file, instr_bkgnd=False, foreground=False, ptsrc_bkgnd=False, bkgnd_area=None, absorb_model="wabs", nH=0.05, overwrite=False, prng=None): """ Generate a PI or PHA spectrum from a :class:`~soxs.spectra.Spectrum` by convolving it with responses. To be used if one wants to create a spectrum without worrying about spatial response. Similar to XSPEC's "fakeit". Parameters ---------- spec : :class:`~soxs.spectra.Spectrum` The spectrum to be convolved. If None is supplied, only backgrounds will be simulated (if they are turned on). instrument : string The name of the instrument to use, which picks an instrument specification from the instrument registry. exp_time : float, (value, unit) tuple, or :class:`~astropy.units.Quantity` The exposure time in seconds. out_file : string The file to write the spectrum to. instr_bkgnd : boolean, optional Whether or not to include the instrumental/particle background. Default: False foreground : boolean, optional Whether or not to include the local foreground. Default: False ptsrc_bkgnd : boolean, optional Whether or not to include the unresolved point-source background. Default: False bkgnd_area : float, (value, unit) tuple, or :class:`~astropy.units.Quantity` The area on the sky for the background components, in square arcminutes. Default: None, necessary to specify if any of the background components are turned on. absorb_model : string, optional The absorption model to use, "wabs" or "tbabs". Default: "wabs" nH : float, optional The hydrogen column in units of 10**22 atoms/cm**2. Default: 0.05 overwrite : boolean, optional Whether or not to overwrite an existing file. Default: False prng : :class:`~numpy.random.RandomState` object, integer, or None A pseudo-random number generator. Typically will only be specified if you have a reason to generate the same set of random numbers, such as for a test. Default is None, which sets the seed based on the system time. Examples -------- >>> spec = soxs.Spectrum.from_file("my_spectrum.txt") >>> soxs.simulate_spectrum(spec, "lynx_lxm", 100000.0, ... "my_spec.pi", overwrite=True) """ from soxs.events import _write_spectrum from soxs.instrument import RedistributionMatrixFile, \ AuxiliaryResponseFile from soxs.spectra import ConvolvedSpectrum from soxs.background.foreground import hm_astro_bkgnd from soxs.background.instrument import instrument_backgrounds from soxs.background.spectra import BackgroundSpectrum prng = parse_prng(prng) exp_time = parse_value(exp_time, "s") try: instrument_spec = instrument_registry[instrument] except KeyError: raise KeyError("Instrument %s is not in the instrument registry!" % instrument) if foreground or instr_bkgnd or ptsrc_bkgnd: if instrument_spec["grating"]: raise NotImplementedError("Backgrounds cannot be included in simulations " "of gratings spectra at this time!") if bkgnd_area is None: raise RuntimeError("The 'bkgnd_area' argument must be set if one wants " "to simulate backgrounds! Specify a value in square " "arcminutes.") bkgnd_area = np.sqrt(parse_value(bkgnd_area, "arcmin**2")) elif spec is None: raise RuntimeError("You have specified no source spectrum and no backgrounds!") arf_file = get_response_path(instrument_spec["arf"]) rmf_file = get_response_path(instrument_spec["rmf"]) arf = AuxiliaryResponseFile(arf_file) rmf = RedistributionMatrixFile(rmf_file) event_params = {} event_params["RESPFILE"] = os.path.split(rmf.filename)[-1] event_params["ANCRFILE"] = os.path.split(arf.filename)[-1] event_params["TELESCOP"] = rmf.header["TELESCOP"] event_params["INSTRUME"] = rmf.header["INSTRUME"] event_params["MISSION"] = rmf.header.get("MISSION", "") out_spec = np.zeros(rmf.n_ch) if spec is not None: cspec = ConvolvedSpectrum(spec, arf) out_spec += rmf.convolve_spectrum(cspec, exp_time, prng=prng) fov = None if bkgnd_area is None else np.sqrt(bkgnd_area) if foreground: mylog.info("Adding in astrophysical foreground.") cspec_frgnd = ConvolvedSpectrum(hm_astro_bkgnd.to_spectrum(fov), arf) out_spec += rmf.convolve_spectrum(cspec_frgnd, exp_time, prng=prng) if instr_bkgnd and instrument_spec["bkgnd"] is not None: mylog.info("Adding in instrumental background.") instr_spec = instrument_backgrounds[instrument_spec["bkgnd"]] cspec_instr = instr_spec.to_scaled_spectrum(fov, instrument_spec["focal_length"]) out_spec += rmf.convolve_spectrum(cspec_instr, exp_time, prng=prng) if ptsrc_bkgnd: mylog.info("Adding in background from unresolved point-sources.") spec_plaw = BackgroundSpectrum.from_powerlaw(1.45, 0.0, 2.0e-7, emin=0.01, emax=10.0, nbins=300000) spec_plaw.apply_foreground_absorption(nH, model=absorb_model) cspec_plaw = ConvolvedSpectrum(spec_plaw.to_spectrum(fov), arf) out_spec += rmf.convolve_spectrum(cspec_plaw, exp_time, prng=prng) bins = (np.arange(rmf.n_ch)+rmf.cmin).astype("int32") _write_spectrum(bins, out_spec, exp_time, rmf.header["CHANTYPE"], event_params, out_file, overwrite=overwrite)
def simulate_spectrum(spec, instrument, exp_time, out_file, instr_bkgnd=False, foreground=False, ptsrc_bkgnd=False, bkgnd_area=None, absorb_model="wabs", nH=0.05, overwrite=False, prng=None): """ Generate a PI or PHA spectrum from a :class:`~soxs.spectra.Spectrum` by convolving it with responses. To be used if one wants to create a spectrum without worrying about spatial response. Similar to XSPEC's "fakeit". Parameters ---------- spec : :class:`~soxs.spectra.Spectrum` The spectrum to be convolved. If None is supplied, only backgrounds will be simulated (if they are turned on). instrument : string The name of the instrument to use, which picks an instrument specification from the instrument registry. exp_time : float, (value, unit) tuple, or :class:`~astropy.units.Quantity` The exposure time in seconds. out_file : string The file to write the spectrum to. instr_bkgnd : boolean, optional Whether or not to include the instrumental/particle background. Default: False foreground : boolean, optional Whether or not to include the local foreground. Default: False ptsrc_bkgnd : boolean, optional Whether or not to include the unresolved point-source background. Default: False bkgnd_area : float, (value, unit) tuple, or :class:`~astropy.units.Quantity` The area on the sky for the background components, in square arcminutes. Default: None, necessary to specify if any of the background components are turned on. absorb_model : string, optional The absorption model to use, "wabs" or "tbabs". Default: "wabs" nH : float, optional The hydrogen column in units of 10**22 atoms/cm**2. Default: 0.05 overwrite : boolean, optional Whether or not to overwrite an existing file. Default: False prng : :class:`~numpy.random.RandomState` object, integer, or None A pseudo-random number generator. Typically will only be specified if you have a reason to generate the same set of random numbers, such as for a test. Default is None, which sets the seed based on the system time. Examples -------- >>> spec = soxs.Spectrum.from_file("my_spectrum.txt") >>> soxs.simulate_spectrum(spec, "lynx_lxm", 100000.0, ... "my_spec.pi", overwrite=True) """ from soxs.events import _write_spectrum from soxs.instrument import RedistributionMatrixFile, \ AuxiliaryResponseFile from soxs.spectra import ConvolvedSpectrum from soxs.background.foreground import hm_astro_bkgnd from soxs.background.instrument import instrument_backgrounds from soxs.background.spectra import BackgroundSpectrum, \ ConvolvedBackgroundSpectrum prng = parse_prng(prng) exp_time = parse_value(exp_time, "s") try: instrument_spec = instrument_registry[instrument] except KeyError: raise KeyError("Instrument %s is not in the instrument registry!" % instrument) if foreground or instr_bkgnd or ptsrc_bkgnd: if instrument_spec["grating"]: raise NotImplementedError("Backgrounds cannot be included in simulations " "of gratings spectra at this time!") if bkgnd_area is None: raise RuntimeError("The 'bkgnd_area' argument must be set if one wants " "to simulate backgrounds! Specify a value in square " "arcminutes.") bkgnd_area = np.sqrt(parse_value(bkgnd_area, "arcmin**2")) elif spec is None: raise RuntimeError("You have specified no source spectrum and no backgrounds!") arf_file = get_response_path(instrument_spec["arf"]) rmf_file = get_response_path(instrument_spec["rmf"]) arf = AuxiliaryResponseFile(arf_file) rmf = RedistributionMatrixFile(rmf_file) event_params = {} event_params["RESPFILE"] = os.path.split(rmf.filename)[-1] event_params["ANCRFILE"] = os.path.split(arf.filename)[-1] event_params["TELESCOP"] = rmf.header["TELESCOP"] event_params["INSTRUME"] = rmf.header["INSTRUME"] event_params["MISSION"] = rmf.header.get("MISSION", "") out_spec = np.zeros(rmf.n_ch) if spec is not None: cspec = ConvolvedSpectrum(spec, arf) out_spec += rmf.convolve_spectrum(cspec, exp_time, prng=prng) fov = None if bkgnd_area is None else np.sqrt(bkgnd_area) if foreground: mylog.info("Adding in astrophysical foreground.") cspec_frgnd = ConvolvedSpectrum(hm_astro_bkgnd.to_spectrum(fov), arf) out_spec += rmf.convolve_spectrum(cspec_frgnd, exp_time, prng=prng) if instr_bkgnd and instrument_spec["bkgnd"] is not None: mylog.info("Adding in instrumental background.") instr_spec = instrument_backgrounds[instrument_spec["bkgnd"]] cspec_instr = instr_spec.to_scaled_spectrum(fov, instrument_spec["focal_length"]) out_spec += rmf.convolve_spectrum(cspec_instr, exp_time, prng=prng) if ptsrc_bkgnd: mylog.info("Adding in background from unresolved point-sources.") spec_plaw = BackgroundSpectrum.from_powerlaw(1.45, 0.0, 2.0e-7, emin=0.01, emax=10.0, nbins=300000) spec_plaw.apply_foreground_absorption(nH, model=absorb_model) cspec_plaw = ConvolvedBackgroundSpectrum(spec_plaw.to_spectrum(fov), arf) out_spec += rmf.convolve_spectrum(cspec_plaw, exp_time, prng=prng) bins = (np.arange(rmf.n_ch)+rmf.cmin).astype("int32") _write_spectrum(bins, out_spec, exp_time, rmf.header["CHANTYPE"], event_params, out_file, overwrite=overwrite)
def write_spectrum(evtfile, specfile, overwrite=False): r""" Bin event energies into a spectrum and write it to a FITS binary table. Does not do any grouping of channels, and will automatically determine PI or PHA. Parameters ---------- evtfile : string The name of the event file to read the events from. specfile : string The name of the spectrum file to be written. overwrite : boolean, optional Whether or not to overwrite an existing file with the same name. Default: False """ from soxs.instrument import RedistributionMatrixFile parameters = {} if isinstance(evtfile, string_types): f = pyfits.open(evtfile) spectype = f["EVENTS"].header["CHANTYPE"] rmf = f["EVENTS"].header["RESPFILE"] p = f["EVENTS"].data[spectype] exp_time = f["EVENTS"].header["EXPOSURE"] for key in ["RESPFILE", "ANCRFILE", "MISSION", "TELESCOP", "INSTRUME"]: parameters[key] = f["EVENTS"].header[key] f.close() else: rmf = evtfile["rmf"] spectype = evtfile["channel_type"] p = evtfile[spectype] parameters["RESPFILE"] = os.path.split(rmf)[-1] parameters["ANCRFILE"] = os.path.split(evtfile["arf"])[-1] parameters["TELESCOP"] = evtfile["telescope"] parameters["INSTRUME"] = evtfile["instrument"] parameters["MISSION"] = evtfile["mission"] exp_time = evtfile["exposure_time"] rmf = RedistributionMatrixFile(rmf) minlength = rmf.n_ch if rmf.cmin == 1: minlength += 1 spec = np.bincount(p, minlength=minlength) if rmf.cmin == 1: spec = spec[1:] bins = (np.arange(rmf.n_ch) + rmf.cmin).astype("int32") col1 = pyfits.Column(name='CHANNEL', format='1J', array=bins) col2 = pyfits.Column(name=spectype.upper(), format='1D', array=bins.astype("float64")) col3 = pyfits.Column(name='COUNTS', format='1J', array=spec.astype("int32")) col4 = pyfits.Column(name='COUNT_RATE', format='1D', array=spec / exp_time) coldefs = pyfits.ColDefs([col1, col2, col3, col4]) tbhdu = pyfits.BinTableHDU.from_columns(coldefs) tbhdu.name = "SPECTRUM" tbhdu.header["DETCHANS"] = spec.size tbhdu.header["TOTCTS"] = spec.sum() tbhdu.header["EXPOSURE"] = exp_time tbhdu.header["LIVETIME"] = exp_time tbhdu.header["CONTENT"] = spectype tbhdu.header["HDUCLASS"] = "OGIP" tbhdu.header["HDUCLAS1"] = "SPECTRUM" tbhdu.header["HDUCLAS2"] = "TOTAL" tbhdu.header["HDUCLAS3"] = "TYPE:I" tbhdu.header["HDUCLAS4"] = "COUNT" tbhdu.header["HDUVERS"] = "1.1.0" tbhdu.header["HDUVERS1"] = "1.1.0" tbhdu.header["CHANTYPE"] = spectype tbhdu.header["BACKFILE"] = "none" tbhdu.header["CORRFILE"] = "none" tbhdu.header["POISSERR"] = True for key in ["RESPFILE", "ANCRFILE", "MISSION", "TELESCOP", "INSTRUME"]: tbhdu.header[key] = parameters[key] tbhdu.header["AREASCAL"] = 1.0 tbhdu.header["CORRSCAL"] = 0.0 tbhdu.header["BACKSCAL"] = 1.0 hdulist = pyfits.HDUList([pyfits.PrimaryHDU(), tbhdu]) hdulist.writeto(specfile, overwrite=overwrite)
def write_channel_spectrum(self, specfile, overwrite=False): r""" Bin event channels into a spectrum and write it to a FITS binary table. Parameters ---------- specfile : string The name of the FITS file to be written. overwrite : boolean, optional Set to True to overwrite a previous file. """ spectype = self.parameters["channel_type"] rmf = RedistributionMatrixFile(self.parameters["rmf"]) minlength = rmf.n_ch if rmf.cmin == 1: minlength += 1 spec = np.bincount(self[spectype], minlength=minlength) if rmf.cmin == 1: spec = spec[1:] bins = (np.arange(rmf.n_ch) + rmf.cmin).astype("int32") if parallel_capable: spec = comm.comm.reduce(spec, root=0) if comm.rank == 0: col1 = pyfits.Column(name='CHANNEL', format='1J', array=bins) col2 = pyfits.Column(name=spectype.upper(), format='1D', array=bins.astype("float64")) col3 = pyfits.Column(name='COUNTS', format='1J', array=spec.astype("int32")) col4 = pyfits.Column(name='COUNT_RATE', format='1D', array=spec / float(self.parameters["exp_time"])) coldefs = pyfits.ColDefs([col1, col2, col3, col4]) tbhdu = pyfits.BinTableHDU.from_columns(coldefs) tbhdu.name = "SPECTRUM" tbhdu.header["DETCHANS"] = spec.shape[0] tbhdu.header["TOTCTS"] = spec.sum() tbhdu.header["EXPOSURE"] = float(self.parameters["exp_time"]) tbhdu.header["LIVETIME"] = float(self.parameters["exp_time"]) tbhdu.header["CONTENT"] = spectype tbhdu.header["HDUCLASS"] = "OGIP" tbhdu.header["HDUCLAS1"] = "SPECTRUM" tbhdu.header["HDUCLAS2"] = "TOTAL" tbhdu.header["HDUCLAS3"] = "TYPE:I" tbhdu.header["HDUCLAS4"] = "COUNT" tbhdu.header["HDUVERS"] = "1.1.0" tbhdu.header["HDUVERS1"] = "1.1.0" tbhdu.header["CHANTYPE"] = spectype tbhdu.header["BACKFILE"] = "none" tbhdu.header["CORRFILE"] = "none" tbhdu.header["POISSERR"] = True tbhdu.header["RESPFILE"] = os.path.split( self.parameters["rmf"])[-1] tbhdu.header["ANCRFILE"] = os.path.split( self.parameters["arf"])[-1] tbhdu.header["MISSION"] = self.parameters["mission"] tbhdu.header["TELESCOP"] = self.parameters["telescope"] tbhdu.header["INSTRUME"] = self.parameters["instrument"] tbhdu.header["AREASCAL"] = 1.0 tbhdu.header["CORRSCAL"] = 0.0 tbhdu.header["BACKSCAL"] = 1.0 hdulist = pyfits.HDUList([pyfits.PrimaryHDU(), tbhdu]) hdulist.writeto(specfile, overwrite=overwrite) comm.barrier()
def plot_spectrum(specfile, plot_energy=True, lw=2, fig=None, ax=None): """ Make a quick Matplotlib plot of a convolved spectrum from a file. A Matplotlib figure is returned. Parameters ---------- specfile : string The file to be opened for plotting. figsize : tuple of integers, optional The size of the figure on both sides in inches. Default: (10,10) plot_energy : boolean, optional Whether to plot in energy or channel space. Default is to plot in energy, unless the RMF for the spectrum cannot be found. lw : float, optional The width of the lines in the plots. Default: 2.0 px. fig : :class:`~matplotlib.figure.Figure`, optional A Figure instance to plot in. Default: None, one will be created if not provided. ax : :class:`~matplotlib.axes.Axes`, optional An Axes instance to plot in. Default: None, one will be created if not provided. Returns ------- The :class:`~matplotlib.figure.Figure` object. """ import matplotlib.pyplot as plt from soxs.instrument import RedistributionMatrixFile f = pyfits.open(specfile) hdu = f["SPECTRUM"] chantype = hdu.header["CHANTYPE"] rmf = hdu.header.get("RESPFILE", None) xerr = None if plot_energy: if rmf is not None: rmf = RedistributionMatrixFile(rmf) x = 0.5 * (rmf.ebounds["E_MIN"] + rmf.ebounds["E_MAX"]) xerr = 0.5 * (rmf.ebounds["E_MAX"] - rmf.ebounds["E_MIN"]) xlabel = "E (keV)" else: raise RuntimeError("Cannot find the RMF associated with this " "spectrum, so I cannot plot in energy!") else: x = hdu.data[chantype] xlabel = "Channel (%s)" % chantype y = hdu.data["COUNT_RATE"] yerr = np.sqrt(hdu.data["COUNTS"]) / hdu.header["EXPOSURE"] if plot_energy: yunit = "keV" y /= 2.0 * xerr yerr /= 2.0 * xerr else: yunit = "bin" f.close() if fig is None: fig = plt.figure(figsize=(10, 10)) if ax is None: ax = fig.add_subplot(111) ax.errorbar(x, y, yerr=yerr, xerr=xerr, lw=lw) ax.set_xlabel(xlabel) ax.set_ylabel("Count Rate (counts/s/%s)" % yunit) return fig
from soxs.spectra import ApecGenerator from soxs.spatial import PointSourceModel from soxs.simput import SimputCatalog from soxs.instrument_registry import \ get_instrument_from_registry from soxs.instrument import instrument_simulator, \ RedistributionMatrixFile, simulate_spectrum from soxs.events import write_spectrum from numpy.random import RandomState from numpy.testing import assert_allclose from soxs.tests.utils import spectrum_answer_testing, \ file_answer_testing inst_name = "lynx_lxm" rmf = RedistributionMatrixFile.from_instrument(inst_name) agen0 = ApecGenerator(0.01, 10.0, 20000, broadening=True) agen_var0 = ApecGenerator(0.01, 10.0, 20000, var_elem=["O", "Fe"], broadening=True) agen_nolines0 = ApecGenerator(0.01, 10.0, 20000, broadening=True, nolines=True) agen_aspl0 = ApecGenerator(0.01, 10.0, 20000, broadening=True, abund_table="aspl") agen = ApecGenerator(rmf.elo[0], rmf.ehi[-1], rmf.n_e, broadening=True) agen_var = ApecGenerator(rmf.elo[0], rmf.ehi[-1],
def simulate_spectrum(spec, instrument, exp_time, out_file, overwrite=False, prng=None): """ Generate a PI or PHA spectrum from a :class:`~soxs.spectra.Spectrum` by convolving it with responses. To be used if one wants to create a spectrum without worrying about spatial response. Similar to XSPEC's "fakeit". Parameters ---------- spec : :class:`~soxs.spectra.Spectrum` The spectrum to be convolved. instrument : string The name of the instrument to use, which picks an instrument specification from the instrument registry. exp_time : float, (value, unit) tuple, or :class:`~astropy.units.Quantity` The exposure time in seconds. out_file : string The file to write the spectrum to. overwrite : boolean, optional Whether or not to overwrite an existing file. Default: False prng : :class:`~numpy.random.RandomState` object, integer, or None A pseudo-random number generator. Typically will only be specified if you have a reason to generate the same set of random numbers, such as for a test. Default is None, which sets the seed based on the system time. Examples -------- >>> spec = soxs.Spectrum.from_file("my_spectrum.txt") >>> soxs.simulate_spectrum(spec, "mucal", 100000.0, ... "my_spec.pi", overwrite=True) """ from soxs.events import write_spectrum from soxs.instrument import RedistributionMatrixFile, \ AuxiliaryResponseFile from soxs.spectra import ConvolvedSpectrum prng = parse_prng(prng) exp_time = parse_value(exp_time, "s") try: instrument_spec = instrument_registry[instrument] except KeyError: raise KeyError("Instrument %s is not in the instrument registry!" % instrument) arf_file = check_file_location(instrument_spec["arf"], "files") rmf_file = check_file_location(instrument_spec["rmf"], "files") arf = AuxiliaryResponseFile(arf_file) rmf = RedistributionMatrixFile(rmf_file) cspec = ConvolvedSpectrum(spec, arf) events = {} events["energy"] = cspec.generate_energies(exp_time, prng=prng).value events = rmf.scatter_energies(events, prng=prng) events["arf"] = arf.filename events["rmf"] = rmf.filename events["exposure_time"] = exp_time events["channel_type"] = rmf.header["CHANTYPE"] events["telescope"] = rmf.header["TELESCOP"] events["instrument"] = rmf.header["INSTRUME"] events["mission"] = rmf.header.get("MISSION", "") write_spectrum(events, out_file, overwrite=overwrite)