def __init__(self, spectrum, arf): """ Generate a convolved spectrum by convolving a spectrum with an ARF. Parameters ---------- spectrum : :class:`~soxs.spectra.Spectrum` object The input spectrum to convolve with. arf : string or :class:`~soxs.instrument.AuxiliaryResponseFile` The ARF to use in the convolution. """ if not isinstance(arf, AuxiliaryResponseFile): arf = AuxiliaryResponseFile(arf) self.arf = arf earea = arf.interpolate_area(spectrum.emid) rate = spectrum.flux * earea super(ConvolvedSpectrum, self).__init__(spectrum.ebins, rate)
def __init__(self, spectrum, arf): """ Generate a convolved spectrum by convolving a spectrum with an ARF. Parameters ---------- spectrum : :class:`~soxs.spectra.Spectrum` object The input spectrum to convolve with. arf : string or :class:`~soxs.instrument.AuxiliaryResponseFile` The ARF to use in the convolution. """ if not isinstance(arf, AuxiliaryResponseFile): arf = AuxiliaryResponseFile(arf) self.arf = arf earea = arf.interpolate_area(spectrum.emid) rate = spectrum.flux * earea super(ConvolvedSpectrum, self).__init__(spectrum.ebins, rate)
def make_exposure_map(event_file, expmap_file, energy, weights=None, asol_file=None, normalize=True, overwrite=False, nhistx=16, nhisty=16): """ Make an exposure map for a SOXS event file, and optionally write an aspect solution file. The exposure map will be created by binning an aspect histogram over the range of the aspect solution. Parameters ---------- event_file : string The path to the event file to use for making the exposure map. expmap_file : string The path to write the exposure map file to. energy : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`, or NumPy array The energy in keV to use when computing the exposure map, or a set of energies to be used with the *weights* parameter. If providing a set, it must be in keV. weights : array-like, optional The weights to use with a set of energies given in the *energy* parameter. Used to create a more accurate exposure map weighted by a range of energies. Default: None asol_file : string, optional The path to write the aspect solution file to, if desired. Default: None normalize : boolean, optional If True, the exposure map will be divided by the exposure time so that the map's units are cm**2. Default: True overwrite : boolean, optional Whether or not to overwrite an existing file. Default: False nhistx : integer, optional The number of bins in the aspect histogram in the DETX direction. Default: 16 nhisty : integer, optional The number of bins in the aspect histogram in the DETY direction. Default: 16 """ import pyregion._region_filter as rfilter from scipy.ndimage.interpolation import rotate, shift from soxs.instrument import AuxiliaryResponseFile, perform_dither if isinstance(energy, np.ndarray) and weights is None: raise RuntimeError("Must supply a single value for the energy if " "you do not supply weights!") if not isinstance(energy, np.ndarray): energy = parse_value(energy, "keV") f_evt = pyfits.open(event_file) hdu = f_evt["EVENTS"] arf = AuxiliaryResponseFile(hdu.header["ANCRFILE"]) exp_time = hdu.header["EXPOSURE"] nx = int(hdu.header["TLMAX2"] - 0.5) // 2 ny = int(hdu.header["TLMAX3"] - 0.5) // 2 ra0 = hdu.header["TCRVL2"] dec0 = hdu.header["TCRVL3"] xdel = hdu.header["TCDLT2"] ydel = hdu.header["TCDLT3"] x0 = hdu.header["TCRPX2"] y0 = hdu.header["TCRPX3"] xdet0 = 0.5 * (2 * nx + 1) ydet0 = 0.5 * (2 * ny + 1) xaim = hdu.header.get("AIMPT_X", 0.0) yaim = hdu.header.get("AIMPT_Y", 0.0) roll = hdu.header["ROLL_PNT"] instr = instrument_registry[hdu.header["INSTRUME"].lower()] 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"] = ydel * 3600.0 dither_params["dither_on"] = True else: dither_params["dither_on"] = False f_evt.close() # Create time array for aspect solution dt = 1.0 # Seconds t = np.arange(0.0, exp_time + dt, dt) # Construct WCS w = pywcs.WCS(naxis=2) w.wcs.crval = [ra0, dec0] w.wcs.crpix = [x0, y0] w.wcs.cdelt = [xdel, ydel] w.wcs.ctype = ["RA---TAN", "DEC--TAN"] w.wcs.cunit = ["deg"] * 2 # Create aspect solution if we had dithering. # otherwise just set the offsets to zero if dither_params["dither_on"]: x_off, y_off = perform_dither(t, dither_params) # Make the aspect histogram x_amp = dither_params["x_amp"] / dither_params["plate_scale"] y_amp = dither_params["y_amp"] / dither_params["plate_scale"] x_edges = np.linspace(-x_amp, x_amp, nhistx + 1, endpoint=True) y_edges = np.linspace(-y_amp, y_amp, nhisty + 1, endpoint=True) asphist = np.histogram2d(x_off, y_off, (x_edges, y_edges))[0] asphist *= dt x_mid = 0.5 * (x_edges[1:] + x_edges[:-1]) y_mid = 0.5 * (y_edges[1:] + y_edges[:-1]) # Determine the effective area eff_area = arf.interpolate_area(energy).value if weights is not None: eff_area = np.average(eff_area, weights=weights) if instr["chips"] is None: rtypes = ["Box"] args = [[0.0, 0.0, instr["num_pixels"], instr["num_pixels"]]] else: rtypes = [] args = [] for i, chip in enumerate(instr["chips"]): rtypes.append(chip[0]) args.append(np.array(chip[1:])) tmpmap = np.zeros((2 * nx, 2 * ny)) for rtype, arg in zip(rtypes, args): rfunc = getattr(rfilter, rtype) new_args = parse_region_args(rtype, arg, xdet0 - xaim - 1.0, ydet0 - yaim - 1.0) r = rfunc(*new_args) tmpmap += r.mask(tmpmap).astype("float64") if dither_params["dither_on"]: expmap = np.zeros((2 * nx, 2 * ny)) niter = nhistx * nhisty pbar = tqdm(leave=True, total=niter, desc="Creating exposure map ") for i in range(nhistx): for j in range(nhisty): expmap += shift(tmpmap, (x_mid[i], y_mid[j]), order=0) * asphist[i, j] pbar.update(nhisty) pbar.close() else: expmap = tmpmap * exp_time expmap *= eff_area if normalize: expmap /= exp_time if roll != 0.0: rotate(expmap, roll, output=expmap, reshape=False) map_header = { "EXPOSURE": exp_time, "MTYPE1": "EQPOS", "MFORM1": "RA,DEC", "CTYPE1": "RA---TAN", "CTYPE2": "DEC--TAN", "CRVAL1": ra0, "CRVAL2": dec0, "CUNIT1": "deg", "CUNIT2": "deg", "CDELT1": xdel, "CDELT2": ydel, "CRPIX1": x0, "CRPIX2": y0 } map_hdu = pyfits.ImageHDU(expmap, header=pyfits.Header(map_header)) map_hdu.name = "EXPMAP" map_hdu.writeto(expmap_file, overwrite=overwrite) if asol_file is not None: if dither_params["dither_on"]: det = np.array([x_off, y_off]) pix = np.dot(get_rot_mat(roll).T, det) ra, dec = w.wcs_pix2world(pix[0, :] + x0, pix[1, :] + y0, 1) col_t = pyfits.Column(name='time', format='D', unit='s', array=t) col_ra = pyfits.Column(name='ra', format='D', unit='deg', array=ra) col_dec = pyfits.Column(name='dec', format='D', unit='deg', array=dec) coldefs = pyfits.ColDefs([col_t, col_ra, col_dec]) tbhdu = pyfits.BinTableHDU.from_columns(coldefs) tbhdu.name = "ASPSOL" tbhdu.header["EXPOSURE"] = exp_time hdulist = [pyfits.PrimaryHDU(), tbhdu] pyfits.HDUList(hdulist).writeto(asol_file, overwrite=overwrite) else: mylog.warning("Refusing to write an aspect solution file because " "there was no dithering.")
def make_exposure_map(event_file, expmap_file, energy, weights=None, asol_file=None, normalize=True, overwrite=False, reblock=1, nhistx=16, nhisty=16, order=1): """ Make an exposure map for a SOXS event file, and optionally write an aspect solution file. The exposure map will be created by binning an aspect histogram over the range of the aspect solution. Parameters ---------- event_file : string The path to the event file to use for making the exposure map. expmap_file : string The path to write the exposure map file to. energy : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`, or NumPy array The energy in keV to use when computing the exposure map, or a set of energies to be used with the *weights* parameter. If providing a set, it must be in keV. weights : array-like, optional The weights to use with a set of energies given in the *energy* parameter. Used to create a more accurate exposure map weighted by a range of energies. Default: None asol_file : string, optional The path to write the aspect solution file to, if desired. Default: None normalize : boolean, optional If True, the exposure map will be divided by the exposure time so that the map's units are cm**2. Default: True overwrite : boolean, optional Whether or not to overwrite an existing file. Default: False reblock : integer, optional Supply an integer power of 2 here to make an exposure map with a different binning. Default: 1 nhistx : integer, optional The number of bins in the aspect histogram in the DETX direction. Default: 16 nhisty : integer, optional The number of bins in the aspect histogram in the DETY direction. Default: 16 order : integer, optional The interpolation order to use when making the exposure map. Default: 1 """ import pyregion._region_filter as rfilter from scipy.ndimage.interpolation import rotate, shift from soxs.instrument import AuxiliaryResponseFile, perform_dither if isinstance(energy, np.ndarray) and weights is None: raise RuntimeError("Must supply a single value for the energy if " "you do not supply weights!") if not isinstance(energy, np.ndarray): energy = parse_value(energy, "keV") f_evt = pyfits.open(event_file) hdu = f_evt["EVENTS"] arf = AuxiliaryResponseFile(hdu.header["ANCRFILE"]) exp_time = hdu.header["EXPOSURE"] nx = int(hdu.header["TLMAX2"]-0.5)//2 ny = int(hdu.header["TLMAX3"]-0.5)//2 ra0 = hdu.header["TCRVL2"] dec0 = hdu.header["TCRVL3"] xdel = hdu.header["TCDLT2"] ydel = hdu.header["TCDLT3"] x0 = hdu.header["TCRPX2"] y0 = hdu.header["TCRPX3"] xdet0 = 0.5*(2*nx+1) ydet0 = 0.5*(2*ny+1) xaim = hdu.header.get("AIMPT_X", 0.0) yaim = hdu.header.get("AIMPT_Y", 0.0) roll = hdu.header["ROLL_PNT"] instr = instrument_registry[hdu.header["INSTRUME"].lower()] 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"] = ydel*3600.0 dither_params["dither_on"] = True else: dither_params["dither_on"] = False f_evt.close() # Create time array for aspect solution dt = 1.0 # Seconds t = np.arange(0.0, exp_time+dt, dt) # Construct WCS w = pywcs.WCS(naxis=2) w.wcs.crval = [ra0, dec0] w.wcs.crpix = [x0, y0] w.wcs.cdelt = [xdel, ydel] w.wcs.ctype = ["RA---TAN","DEC--TAN"] w.wcs.cunit = ["deg"]*2 # Create aspect solution if we had dithering. # otherwise just set the offsets to zero if dither_params["dither_on"]: x_off, y_off = perform_dither(t, dither_params) # Make the aspect histogram x_amp = dither_params["x_amp"]/dither_params["plate_scale"] y_amp = dither_params["y_amp"]/dither_params["plate_scale"] x_edges = np.linspace(-x_amp, x_amp, nhistx+1, endpoint=True) y_edges = np.linspace(-y_amp, y_amp, nhisty+1, endpoint=True) asphist = np.histogram2d(x_off, y_off, (x_edges, y_edges))[0] asphist *= dt x_mid = 0.5*(x_edges[1:]+x_edges[:-1])/reblock y_mid = 0.5*(y_edges[1:]+y_edges[:-1])/reblock # Determine the effective area eff_area = arf.interpolate_area(energy).value if weights is not None: eff_area = np.average(eff_area, weights=weights) if instr["chips"] is None: rtypes = ["Box"] args = [[0.0, 0.0, instr["num_pixels"], instr["num_pixels"]]] else: rtypes = [] args = [] for i, chip in enumerate(instr["chips"]): rtypes.append(chip[0]) args.append(np.array(chip[1:])) tmpmap = np.zeros((2*nx, 2*ny)) for rtype, arg in zip(rtypes, args): rfunc = getattr(rfilter, rtype) new_args = parse_region_args(rtype, arg, xdet0-xaim-1.0, ydet0-yaim-1.0) r = rfunc(*new_args) tmpmap += r.mask(tmpmap).astype("float64") tmpmap = downsample(tmpmap, reblock) if dither_params["dither_on"]: expmap = np.zeros(tmpmap.shape) niter = nhistx*nhisty pbar = tqdm(leave=True, total=niter, desc="Creating exposure map ") for i in range(nhistx): for j in range(nhisty): expmap += shift(tmpmap, (x_mid[i], y_mid[j]), order=order)*asphist[i, j] pbar.update(nhisty) pbar.close() else: expmap = tmpmap*exp_time expmap *= eff_area if normalize: expmap /= exp_time if roll != 0.0: rotate(expmap, roll, output=expmap, reshape=False) expmap[expmap < 0.0] = 0.0 map_header = {"EXPOSURE": exp_time, "MTYPE1": "EQPOS", "MFORM1": "RA,DEC", "CTYPE1": "RA---TAN", "CTYPE2": "DEC--TAN", "CRVAL1": ra0, "CRVAL2": dec0, "CUNIT1": "deg", "CUNIT2": "deg", "CDELT1": xdel*reblock, "CDELT2": ydel*reblock, "CRPIX1": 0.5*(2.0*nx//reblock+1), "CRPIX2": 0.5*(2.0*ny//reblock+1)} map_hdu = pyfits.ImageHDU(expmap, header=pyfits.Header(map_header)) map_hdu.name = "EXPMAP" map_hdu.writeto(expmap_file, overwrite=overwrite) if asol_file is not None: if dither_params["dither_on"]: det = np.array([x_off, y_off]) pix = np.dot(get_rot_mat(roll).T, det) ra, dec = w.wcs_pix2world(pix[0,:]+x0, pix[1,:]+y0, 1) col_t = pyfits.Column(name='time', format='D', unit='s', array=t) col_ra = pyfits.Column(name='ra', format='D', unit='deg', array=ra) col_dec = pyfits.Column(name='dec', format='D', unit='deg', array=dec) coldefs = pyfits.ColDefs([col_t, col_ra, col_dec]) tbhdu = pyfits.BinTableHDU.from_columns(coldefs) tbhdu.name = "ASPSOL" tbhdu.header["EXPOSURE"] = exp_time hdulist = [pyfits.PrimaryHDU(), tbhdu] pyfits.HDUList(hdulist).writeto(asol_file, overwrite=overwrite) else: mylog.warning("Refusing to write an aspect solution file because " "there was no dithering.")