def make_diffuse_background(bkg_events, event_params, rmf, prng=None): from xcs_soxs.instrument import perform_dither n_e = bkg_events["energy"].size bkg_events['time'] = prng.uniform(size=n_e, low=0.0, high=event_params["exposure_time"]) x_offset, y_offset = perform_dither(bkg_events["time"], event_params["dither_params"]) rot_mat = get_rot_mat(event_params["roll_angle"]) det = np.array([ bkg_events["detx"] + x_offset - event_params["aimpt_coords"][0], bkg_events["dety"] + y_offset - event_params["aimpt_coords"][1] ]) pix = np.dot(rot_mat.T, det) bkg_events["xpix"] = pix[0, :] + event_params['pix_center'][0] bkg_events["ypix"] = pix[1, :] + event_params['pix_center'][1] mylog.info("Scattering energies with RMF %s." % os.path.split(rmf.filename)[-1]) bkg_events = rmf.scatter_energies(bkg_events, prng=prng) return bkg_events
def _generate_energies(spec, t_exp, rate, prng, quiet=False): cumspec = spec.cumspec n_ph = prng.poisson(t_exp * rate) if not quiet: mylog.info("Creating %d energies from this spectrum." % n_ph) randvec = prng.uniform(size=n_ph) randvec.sort() e = np.interp(randvec, cumspec, spec.ebins.value) if not quiet: mylog.info("Finished creating energies.") return e
def make_foreground(event_params, arf, rmf, prng=None): import pyregion._region_filter as rfilter prng = parse_prng(prng) conv_frgnd_spec = ConvolvedBackgroundSpectrum(hm_astro_bkgnd, arf) energy = conv_frgnd_spec.generate_energies(event_params["exposure_time"], event_params["fov"], prng=prng, quiet=True).value prng = parse_prng(prng) bkg_events = {} n_events = energy.size nx = event_params["num_pixels"] bkg_events["detx"] = prng.uniform(low=-0.5 * nx, high=0.5 * nx, size=n_events) bkg_events["dety"] = prng.uniform(low=-0.5 * nx, high=0.5 * nx, size=n_events) bkg_events["energy"] = energy if event_params["chips"] is None: bkg_events["chip_id"] = np.zeros(n_events, dtype='int') else: bkg_events["chip_id"] = -np.ones(n_events, dtype='int') for i, chip in enumerate(event_params["chips"]): thisc = np.ones(n_events, dtype='bool') rtype = chip[0] args = chip[1:] r = getattr(rfilter, rtype)(*args) inside = r.inside(bkg_events["detx"], bkg_events["dety"]) thisc = np.logical_and(thisc, inside) bkg_events["chip_id"][thisc] = i keep = bkg_events["chip_id"] > -1 if keep.sum() == 0: raise RuntimeError( "No astrophysical foreground events were detected!!!") else: mylog.info("Making %d events from the astrophysical foreground." % keep.sum()) for key in bkg_events: bkg_events[key] = bkg_events[key][keep] return make_diffuse_background(bkg_events, event_params, rmf, prng=prng)
def write_catalog(self, overwrite=False): """ Write the SIMPUT catalog and associated photon lists to disk. Parameters ---------- overwrite : boolean, optional Whether or not to overwrite an existing file with the same name. Default: False """ for i, phlist in enumerate(self.photon_lists): if i == 0: append = False mylog.info("Writing SIMPUT catalog file %s_simput.fits." % self.name) else: append = True mylog.info("Writing SIMPUT photon list file %s_phlist.fits." % phlist.name) phlist.write_photon_list(self.name, append=append, overwrite=overwrite)
def make_ptsrc_background(exp_time, fov, sky_center, absorb_model="wabs", nH=0.05, area=40000.0, input_sources=None, output_sources=None, prng=None): r""" Make a point-source background. Parameters ---------- exp_time : float, (value, unit) tuple, or :class:`~astropy.units.Quantity` The exposure time of the observation in seconds. fov : float, (value, unit) tuple, or :class:`~astropy.units.Quantity` The field of view in arcminutes. sky_center : array-like The center RA, Dec of the field of view in degrees. absorb_model : string, optional The absorption model to use, "wabs" or "tbabs". Default: "wabs" nH : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`, optional The hydrogen column in units of 10**22 atoms/cm**2. Default: 0.05 area : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`, optional The effective area in cm**2. It must be large enough so that a sufficiently large sample is drawn for the ARF. Default: 40000. input_sources : string, optional If set to a filename, input the source positions, fluxes, and spectral indices from an ASCII table instead of generating them. Default: None output_sources : string, optional If set to a filename, output the properties of the sources within the field of view to a file. Default: None 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. """ prng = parse_prng(prng) exp_time = parse_value(exp_time, "s") fov = parse_value(fov, "arcmin") if nH is not None: nH = parse_value(nH, "1.0e22*cm**-2") area = parse_value(area, "cm**2") if input_sources is None: ra0, dec0, fluxes, ind = generate_sources(exp_time, fov, sky_center, area=area, prng=prng) num_sources = fluxes.size else: mylog.info("Reading in point-source properties from %s." % input_sources) t = ascii.read(input_sources) ra0 = t["RA"].data dec0 = t["Dec"].data fluxes = t["flux_0.5_2.0_keV"].data ind = t["index"].data num_sources = fluxes.size mylog.debug("Generating spectra from %d sources." % num_sources) # If requested, output the source properties to a file if output_sources is not None: t = Table([ra0, dec0, fluxes, ind], names=('RA', 'Dec', 'flux_0.5_2.0_keV', 'index')) t["RA"].unit = "deg" t["Dec"].unit = "deg" t["flux_0.5_2.0_keV"].unit = "erg/(cm**2*s)" t["index"].unit = "" t.write(output_sources, format='ascii.ecsv', overwrite=True) # Pre-calculate for optimization eratio = spec_emax / spec_emin oma = 1.0 - ind invoma = 1.0 / oma invoma[oma == 0.0] = 1.0 fac1 = spec_emin**oma fac2 = spec_emax**oma - fac1 fluxscale = get_flux_scale(ind, fb_emin, fb_emax, spec_emin, spec_emax) # Using the energy flux, determine the photon flux by simple scaling ref_ph_flux = fluxes * fluxscale * keV_per_erg # Now determine the number of photons we will generate n_photons = prng.poisson(ref_ph_flux * exp_time * area) all_energies = [] all_ra = [] all_dec = [] for i, nph in enumerate(n_photons): if nph > 0: # Generate the energies in the source frame u = prng.uniform(size=nph) if ind[i] == 1.0: energies = spec_emin * (eratio**u) else: energies = fac1[i] + u * fac2[i] energies **= invoma[i] # Assign positions for this source ra = ra0[i] * np.ones(nph) dec = dec0[i] * np.ones(nph) all_energies.append(energies) all_ra.append(ra) all_dec.append(dec) mylog.debug("Finished generating spectra.") all_energies = np.concatenate(all_energies) all_ra = np.concatenate(all_ra) all_dec = np.concatenate(all_dec) all_nph = all_energies.size # Remove some of the photons due to Galactic foreground absorption. # We will throw a lot of stuff away, but this is more general and still # faster. if nH is not None: if absorb_model == "wabs": absorb = get_wabs_absorb(all_energies, nH) elif absorb_model == "tbabs": absorb = get_tbabs_absorb(all_energies, nH) randvec = prng.uniform(size=all_energies.size) all_energies = all_energies[randvec < absorb] all_ra = all_ra[randvec < absorb] all_dec = all_dec[randvec < absorb] all_nph = all_energies.size mylog.debug("%d photons remain after foreground galactic absorption." % all_nph) all_flux = np.sum(all_energies) * erg_per_keV / (exp_time * area) output_events = { "ra": all_ra, "dec": all_dec, "energy": all_energies, "flux": all_flux } return output_events
def add_background_from_file(events, event_params, bkg_file): from xcs_soxs.instrument import perform_dither f = pyfits.open(bkg_file) hdu = f["EVENTS"] dither_params = {} if "DITHXAMP" in hdu.header: dither_params["x_amp"] = hdu.header["DITHXAMP"] dither_params["y_amp"] = hdu.header["DITHYAMP"] dither_params["x_period"] = hdu.header["DITHXPER"] dither_params["y_period"] = hdu.header["DITHYPER"] dither_params["plate_scale"] = hdu.header["TCDLT3"] * 3600.0 dither_params["dither_on"] = True else: dither_params["dither_on"] = False sexp = event_params["exposure_time"] bexp = hdu.header["EXPOSURE"] if event_params["exposure_time"] > hdu.header["EXPOSURE"]: raise RuntimeError( "The background file does not have sufficient exposure! Source " "exposure time %g, background exposure time %g." % (sexp, bexp)) for k1, k2 in key_map.items(): if event_params[k1] != hdu.header[k2]: raise RuntimeError("'%s' keyword does not match! %s vs. %s" % (k1, event_params[k1], hdu.header[k2])) rmf1 = os.path.split(event_params["rmf"])[-1] rmf2 = hdu.header["RESPFILE"] arf1 = os.path.split(event_params["arf"])[-1] arf2 = hdu.header["ANCRFILE"] if rmf1 != rmf2: raise RuntimeError("RMFs do not match! %s vs. %s" % (rmf1, rmf2)) if arf1 != arf2: raise RuntimeError("ARFs do not match! %s vs. %s" % (arf1, arf2)) idxs = hdu.data["TIME"] < sexp mylog.info("Adding %d background events from %s." % (idxs.sum(), bkg_file)) if event_params["roll_angle"] == hdu.header["ROLL_PNT"]: xpix = hdu.data["X"][idxs] ypix = hdu.data["Y"][idxs] else: rot_mat = get_rot_mat(event_params["roll_angle"]) if dither_params["dither_on"]: t = hdu.data["TIME"][idxs] x_off, y_off = perform_dither(t, dither_params) else: x_off = 0.0 y_off = 0.0 det = np.array([ hdu.data["DETX"][idxs] + x_off - event_params["aimpt_coords"][0], hdu.data["DETY"][idxs] + y_off - event_params["aimpt_coords"][1] ]) xpix, ypix = np.dot(rot_mat.T, det) xpix += hdu.header["TCRPX2"] ypix += hdu.header["TCRPX3"] all_events = {} for key in [ "detx", "dety", "time", "ccd_id", event_params["channel_type"] ]: all_events[key] = np.concatenate( [events[key], hdu.data[key.upper()][idxs]]) all_events["xpix"] = np.concatenate([events["xpix"], xpix]) all_events["ypix"] = np.concatenate([events["ypix"], ypix]) all_events["energy"] = np.concatenate( [events["energy"], hdu.data["ENERGY"][idxs] / 1000.0]) f.close() return all_events
def write_event_file(events, parameters, filename, overwrite=False): from astropy.time import Time, TimeDelta mylog.info("Writing events to file %s." % filename) t_begin = Time.now() dt = TimeDelta(parameters["exposure_time"], format='sec') t_end = t_begin + dt col_x = pyfits.Column(name='X', format='D', unit='pixel', array=events["xpix"]) col_y = pyfits.Column(name='Y', format='D', unit='pixel', array=events["ypix"]) col_e = pyfits.Column(name='ENERGY', format='E', unit='eV', array=events["energy"] * 1000.) col_dx = pyfits.Column(name='DETX', format='D', unit='pixel', array=events["detx"]) col_dy = pyfits.Column(name='DETY', format='D', unit='pixel', array=events["dety"]) col_id = pyfits.Column(name='CCD_ID', format='D', unit='pixel', array=events["ccd_id"]) chantype = 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]) col_t = pyfits.Column(name="TIME", format='1D', unit='s', array=events['time']) cols = [col_e, col_x, col_y, col_ch, col_t, col_dx, col_dy, col_id] 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"] = parameters["sky_center"][0] tbhdu.header["TCRVL3"] = parameters["sky_center"][1] tbhdu.header["TCDLT2"] = -parameters["plate_scale"] tbhdu.header["TCDLT3"] = parameters["plate_scale"] tbhdu.header["TCRPX2"] = parameters["pix_center"][0] tbhdu.header["TCRPX3"] = parameters["pix_center"][1] tbhdu.header["TCUNI2"] = "deg" tbhdu.header["TCUNI3"] = "deg" tbhdu.header["TLMIN2"] = 0.5 tbhdu.header["TLMIN3"] = 0.5 tbhdu.header["TLMAX2"] = 2.0 * parameters["num_pixels"] + 0.5 tbhdu.header["TLMAX3"] = 2.0 * parameters["num_pixels"] + 0.5 tbhdu.header["TLMIN4"] = parameters["chan_lim"][0] tbhdu.header["TLMAX4"] = parameters["chan_lim"][1] tbhdu.header["TLMIN6"] = -0.5 * parameters["num_pixels"] tbhdu.header["TLMAX6"] = 0.5 * parameters["num_pixels"] tbhdu.header["TLMIN7"] = -0.5 * parameters["num_pixels"] tbhdu.header["TLMAX7"] = 0.5 * parameters["num_pixels"] tbhdu.header["EXPOSURE"] = parameters["exposure_time"] tbhdu.header["TSTART"] = 0.0 tbhdu.header["TSTOP"] = parameters["exposure_time"] 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 tbhdu.header["RESPFILE"] = os.path.split(parameters["rmf"])[-1] tbhdu.header["PHA_BINS"] = parameters["nchan"] tbhdu.header["ANCRFILE"] = os.path.split(parameters["arf"])[-1] tbhdu.header["CHANTYPE"] = parameters["channel_type"] tbhdu.header["MISSION"] = parameters["mission"] tbhdu.header["TELESCOP"] = parameters["telescope"] tbhdu.header["INSTRUME"] = parameters["instrument"] tbhdu.header["RA_PNT"] = parameters["sky_center"][0] tbhdu.header["DEC_PNT"] = parameters["sky_center"][1] tbhdu.header["ROLL_PNT"] = parameters["roll_angle"] tbhdu.header["AIMPT_X"] = parameters["aimpt_coords"][0] tbhdu.header["AIMPT_Y"] = parameters["aimpt_coords"][1] if parameters["dither_params"]["dither_on"]: tbhdu.header["DITHXAMP"] = parameters["dither_params"]["x_amp"] tbhdu.header["DITHYAMP"] = parameters["dither_params"]["y_amp"] tbhdu.header["DITHXPER"] = parameters["dither_params"]["x_period"] tbhdu.header["DITHYPER"] = parameters["dither_params"]["y_period"] 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([parameters["exposure_time"]])) tbhdu_gti = pyfits.BinTableHDU.from_columns([start, stop]) tbhdu_gti.name = "STDGTI" tbhdu_gti.header["TSTART"] = 0.0 tbhdu_gti.header["TSTOP"] = parameters["exposure_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 = [pyfits.PrimaryHDU(), tbhdu, tbhdu_gti] pyfits.HDUList(hdulist).writeto(filename, overwrite=overwrite)
def __init__(self, emin, emax, nbins, var_elem=None, apec_root=None, apec_vers=None, broadening=True, nolines=False, abund_table=None, nei=False): if apec_vers is None: filedir = os.path.join(os.path.dirname(__file__), 'files') cfile = glob.glob("%s/apec_*_coco.fits" % filedir)[0] apec_vers = cfile.split("/")[-1].split("_")[1][1:] mylog.info("Using APEC version %s." % apec_vers) if nei and apec_root is None: raise RuntimeError( "The NEI APEC tables are not supplied with " "SOXS! Download them from http://www.atomdb.org " "and set 'apec_root' to their location.") if nei and var_elem is None: raise RuntimeError( "For NEI spectra, you must specify which elements " "you want to vary using the 'var_elem' argument!") self.nei = nei emin = parse_value(emin, "keV") emax = parse_value(emax, 'keV') self.emin = emin self.emax = emax self.nbins = nbins self.ebins = np.linspace(self.emin, self.emax, nbins + 1) self.de = np.diff(self.ebins) self.emid = 0.5 * (self.ebins[1:] + self.ebins[:-1]) if apec_root is None: apec_root = soxs_files_path if nei: neistr = "_nei" ftype = "comp" else: neistr = "" ftype = "coco" self.cocofile = os.path.join( apec_root, "apec_v%s%s_%s.fits" % (apec_vers, neistr, ftype)) self.linefile = os.path.join( apec_root, "apec_v%s%s_line.fits" % (apec_vers, neistr)) if not os.path.exists(self.cocofile) or not os.path.exists( self.linefile): raise IOError("Cannot find the APEC files!\n %s\n, %s" % (self.cocofile, self.linefile)) mylog.info("Using %s for generating spectral lines." % os.path.split(self.linefile)[-1]) mylog.info("Using %s for generating the continuum." % os.path.split(self.cocofile)[-1]) self.nolines = nolines self.wvbins = hc / self.ebins[::-1] self.broadening = broadening try: self.line_handle = pyfits.open(self.linefile) except IOError: raise IOError("Line file %s does not exist" % self.linefile) try: self.coco_handle = pyfits.open(self.cocofile) except IOError: raise IOError("Continuum file %s does not exist" % self.cocofile) self.Tvals = self.line_handle[1].data.field("kT") self.nT = len(self.Tvals) self.dTvals = np.diff(self.Tvals) self.minlam = self.wvbins.min() self.maxlam = self.wvbins.max() self.var_elem_names = [] self.var_ion_names = [] if var_elem is None: self.var_elem = np.empty((0, 1), dtype='int') else: self.var_elem = [] if len(var_elem) != len(set(var_elem)): raise RuntimeError( "Duplicates were found in the \"var_elem\" list! %s" % var_elem) for elem in var_elem: if "^" in elem: if not self.nei: raise RuntimeError( "Cannot use different ionization states with a " "CIE plasma!") el = elem.split("^") e = el[0] ion = int(el[1]) else: if self.nei: raise RuntimeError( "Variable elements must include the ionization " "state for NEI plasmas!") e = elem ion = 0 self.var_elem.append([elem_names.index(e), ion]) self.var_elem.sort(key=lambda x: (x[0], x[1])) self.var_elem = np.array(self.var_elem, dtype='int') self.var_elem_names = [elem_names[e[0]] for e in self.var_elem] self.var_ion_names = [ "%s^%d" % (elem_names[e[0]], e[1]) for e in self.var_elem ] self.num_var_elem = len(self.var_elem) if self.nei: self.cosmic_elem = [ elem for elem in [1, 2] if elem not in self.var_elem[:, 0] ] self.metal_elem = [] else: self.cosmic_elem = [ elem for elem in cosmic_elem if elem not in self.var_elem[:, 0] ] self.metal_elem = [ elem for elem in metal_elem if elem not in self.var_elem[:, 0] ] if abund_table is None: abund_table = soxs_cfg.get("xcs_soxs", "abund_table") if not isinstance(abund_table, string_types): if len(abund_table) != 30: raise RuntimeError("User-supplied abundance tables " "must be 30 elements long!") self.atable = np.concatenate([[0.0], np.array(abund_table)]) else: self.atable = abund_tables[abund_table].copy() self._atable = self.atable.copy() self._atable[1:] /= abund_tables["angr"][1:]
def make_instrument_background(bkgnd_name, event_params, rmf, prng=None): import pyregion._region_filter as rfilter prng = parse_prng(prng) if event_params["chips"] is None: bkgnd_spec = [instrument_backgrounds[bkgnd_name]] else: if isinstance(bkgnd_name, string_types): nchips = len(event_params["chips"]) bkgnd_names = [bkgnd_name] * nchips else: bkgnd_names = bkgnd_name bkgnd_spec = [] for name in bkgnd_names: spec = instrument_backgrounds[name].new_spec_from_band( rmf.elo[0], rmf.ehi[-1]) bkgnd_spec.append(spec) bkg_events = {} nx = event_params["num_pixels"] if event_params["chips"] is None: bkg_events["energy"] = bkgnd_spec[0].generate_energies( event_params["exposure_time"], event_params["fov"], prng=prng, quiet=True).value n_events = bkg_events["energy"].size bkg_events["chip_id"] = np.zeros(n_events, dtype='int') bkg_events["detx"] = prng.uniform(low=-0.5 * nx, high=0.5 * nx, size=n_events) bkg_events["dety"] = prng.uniform(low=-0.5 * nx, high=0.5 * nx, size=n_events) else: bkg_events["energy"] = [] bkg_events["detx"] = [] bkg_events["dety"] = [] bkg_events["chip_id"] = [] for i, chip in enumerate(event_params["chips"]): e = bkgnd_spec[i].generate_energies(event_params["exposure_time"], event_params["fov"], prng=prng, quiet=True).value n_events = e.size detx = prng.uniform(low=-0.5 * nx, high=0.5 * nx, size=n_events) dety = prng.uniform(low=-0.5 * nx, high=0.5 * nx, size=n_events) thisc = np.ones(n_events, dtype='bool') rtype = chip[0] args = chip[1:] r = getattr(rfilter, rtype)(*args) inside = r.inside(detx, dety) thisc = np.logical_and(thisc, inside) bkg_events["energy"].append(e[thisc]) bkg_events["detx"].append(detx[thisc]) bkg_events["dety"].append(dety[thisc]) bkg_events["chip_id"].append(i * np.ones(thisc.sum())) for key in bkg_events: bkg_events[key] = np.concatenate(bkg_events[key]) if bkg_events["energy"].size == 0: raise RuntimeError( "No instrumental background events were detected!!!") else: mylog.info("Making %d events from the instrumental background." % bkg_events["energy"].size) return make_diffuse_background(bkg_events, event_params, rmf, prng=prng)