Esempio n. 1
0
 def _from_xspec(cls, xspec_in, emin, emax, nbins):
     emin = parse_value(emin, "keV")
     emax = parse_value(emax, "keV")
     tmpdir = tempfile.mkdtemp()
     curdir = os.getcwd()
     os.chdir(tmpdir)
     xspec_in.append("dummyrsp %g %g %d lin\n" % (emin, emax, nbins))
     xspec_in += [
         "set fp [open spec_therm.xspec w+]\n", "tclout energies\n",
         "puts $fp $xspec_tclout\n", "tclout modval\n",
         "puts $fp $xspec_tclout\n", "close $fp\n", "quit\n"
     ]
     f_xin = open("xspec.in", "w")
     f_xin.writelines(xspec_in)
     f_xin.close()
     logfile = os.path.join(curdir, "xspec.log")
     with open(logfile, "ab") as xsout:
         subprocess.call(["xspec", "-", "xspec.in"],
                         stdout=xsout,
                         stderr=xsout)
     f_s = open("spec_therm.xspec", "r")
     lines = f_s.readlines()
     f_s.close()
     ebins = np.array(lines[0].split()).astype("float64")
     de = np.diff(ebins)[0]
     flux = np.array(lines[1].split()).astype("float64") / de
     os.chdir(curdir)
     shutil.rmtree(tmpdir)
     return cls(ebins, flux)
Esempio n. 2
0
 def _spectrum_init(self, kT, velocity, elem_abund):
     kT = parse_value(kT, "keV")
     velocity = parse_value(velocity, "km/s")
     v = velocity * 1.0e5
     tindex = np.searchsorted(self.Tvals, kT) - 1
     dT = (kT - self.Tvals[tindex]) / self.dTvals[tindex]
     return kT, dT, tindex, v
Esempio n. 3
0
    def from_powerlaw(cls, photon_index, redshift, norm, emin, emax, nbins):
        """
        Create a spectrum from a power-law model.

        Parameters
        ----------
        photon_index : float
            The photon index of the source.
        redshift : float
            The redshift of the source.
        norm : float
            The normalization of the source in units of
            photons/s/cm**2/keV at 1 keV in the source 
            frame.
        emin : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`
            The minimum energy of the spectrum in keV. 
        emax : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`
            The maximum energy of the spectrum in keV. 
        nbins : integer
            The number of bins in the spectrum. 
        """
        emin = parse_value(emin, 'keV')
        emax = parse_value(emax, 'keV')
        ebins = np.linspace(emin, emax, nbins + 1)
        emid = 0.5 * (ebins[1:] + ebins[:-1])
        flux = norm * (emid * (1.0 + redshift))**(-photon_index)
        return cls(ebins, flux)
Esempio n. 4
0
    def generate_energies(self, t_exp, fov, prng=None, quiet=False):
        """
        Generate photon energies from this convolved 
        background spectrum given an exposure time and 
        field of view.

        Parameters
        ----------
        t_exp : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`
            The exposure time in seconds.
        fov : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`
            The width of the field of view on a side 
            in arcminutes.
        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. 
        quiet : boolean, optional
            If True, log messages will not be displayed when 
            creating energies. Useful if you have to loop over 
            a lot of spectra. Default: False
        """
        t_exp = parse_value(t_exp, "s")
        fov = parse_value(fov, "arcmin")
        prng = parse_prng(prng)
        rate = fov * fov * self.total_flux.value
        energy = _generate_energies(self, t_exp, rate, prng, quiet=quiet)
        earea = self.arf.interpolate_area(energy).value
        flux = np.sum(energy) * erg_per_keV / t_exp / earea.sum()
        energies = Energies(energy, flux)
        return energies
Esempio n. 5
0
    def get_flux_in_band(self, emin, emax):
        """
        Determine the total flux within a band specified 
        by an energy range. 

        Parameters
        ----------
        emin : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`
            The minimum energy in the band, in keV.
        emax : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`
            The maximum energy in the band, in keV.

        Returns
        -------
        A tuple of values for the flux/intensity in the 
        band: the first value is in terms of the photon 
        rate, the second value is in terms of the energy rate. 
        """
        emin = parse_value(emin, "keV")
        emax = parse_value(emax, "keV")
        range = np.logical_and(self.emid.value >= emin,
                               self.emid.value <= emax)
        pflux = self.flux[range].sum() * self.de
        eflux = (self.flux *
                 self.emid.to("erg"))[range].sum() * self.de / (1.0 * u.photon)
        return pflux, eflux
Esempio n. 6
0
def generate_fluxes(exp_time, area, fov, prng):
    from xcs_soxs.data import cdf_fluxes, cdf_gal, cdf_agn

    exp_time = parse_value(exp_time, "s")
    area = parse_value(area, "cm**2")
    fov = parse_value(fov, "arcmin")

    logf = np.log10(cdf_fluxes)

    n_gal = np.rint(cdf_gal[-1])
    n_agn = np.rint(cdf_agn[-1])
    F_gal = cdf_gal / cdf_gal[-1]
    F_agn = cdf_agn / cdf_agn[-1]
    f_gal = InterpolatedUnivariateSpline(F_gal, logf)
    f_agn = InterpolatedUnivariateSpline(F_agn, logf)

    eph_mean_erg = 1.0 * erg_per_keV

    S_min_obs = eph_mean_erg / (exp_time * area)
    mylog.debug("Flux of %g erg/cm^2/s gives roughly "
                "one photon during exposure." % S_min_obs)
    fov_area = fov**2

    n_gal = int(n_gal * fov_area / 3600.0)
    n_agn = int(n_agn * fov_area / 3600.0)
    mylog.debug("%d AGN, %d galaxies in the FOV." % (n_agn, n_gal))

    randvec1 = prng.uniform(size=n_agn)
    agn_fluxes = 10**f_agn(randvec1)

    randvec2 = prng.uniform(size=n_gal)
    gal_fluxes = 10**f_gal(randvec2)

    return agn_fluxes, gal_fluxes
Esempio n. 7
0
    def generate_energies(self, t_exp, area, prng=None, quiet=False):
        """
        Generate photon energies from this spectrum 
        given an exposure time and effective area.

        Parameters
        ----------
        t_exp : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`
            The exposure time in seconds.
        area : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`
            The effective area in cm**2. If one is creating 
            events for a SIMPUT file, a constant should be 
            used and it must be large enough so that a 
            sufficiently large sample is drawn for the ARF.
        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. 
        quiet : boolean, optional
            If True, log messages will not be displayed when 
            creating energies. Useful if you have to loop over 
            a lot of spectra. Default: False
        """
        t_exp = parse_value(t_exp, "s")
        area = parse_value(area, "cm**2")
        prng = parse_prng(prng)
        rate = area * self.total_flux.value
        energy = _generate_energies(self, t_exp, rate, prng, quiet=quiet)
        flux = np.sum(energy) * erg_per_keV / t_exp / area
        energies = Energies(energy, flux)
        return energies
Esempio n. 8
0
 def to_scaled_spectrum(self, fov, focal_length=None):
     from xcs_soxs.instrument import FlatResponse
     fov = parse_value(fov, "arcmin")
     if focal_length is None:
         focal_length = self.default_focal_length
     else:
         focal_length = parse_value(focal_length, "m")
     flux = self.flux.value * fov * fov
     flux *= (focal_length / self.default_focal_length)**2
     arf = FlatResponse(self.ebins.value[0], self.ebins.value[-1], 1.0,
                        self.ebins.size - 1)
     return ConvolvedSpectrum(Spectrum(self.ebins.value, flux), arf)
Esempio n. 9
0
    def __init__(self, ra0, dec0, r_in, r_out, theta=0.0, ellipticity=1.0):
        r_in = parse_value(r_in, "arcsec")
        r_out = parse_value(r_out, "arcsec")

        def func(r):
            f = np.zeros(r.size)
            idxs = np.logical_and(r >= r_in, r < r_out)
            f[idxs] = 1.0
            return f

        super(AnnulusModel, self).__init__(ra0,
                                           dec0,
                                           func,
                                           theta=theta,
                                           ellipticity=ellipticity)
Esempio n. 10
0
    def apply_foreground_absorption(self, nH, model="wabs", redshift=0.0):
        """
        Given a hydrogen column density, apply
        galactic foreground absorption to the spectrum.

        Parameters
        ----------
        nH : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`
            The hydrogen column in units of 10**22 atoms/cm**2
        model : string, optional
            The model for absorption to use. Options are "wabs"
            (Wisconsin, Morrison and McCammon; ApJ 270, 119) or
            "tbabs" (Tuebingen-Boulder, Wilms, J., Allen, A., & 
            McCray, R. 2000, ApJ, 542, 914). Default: "wabs".
        redshift : float, optional
            The redshift of the absorbing material. Default: 0.0
        """
        nH = parse_value(nH, "1.0e22*cm**-2")
        e = self.emid.value * (1.0 + redshift)
        if model == "wabs":
            sigma = wabs_cross_section(e)
        elif model == "tbabs":
            sigma = tbabs_cross_section(e)
        self.flux *= np.exp(-nH * 1.0e22 * sigma)
        self._compute_total_flux()
Esempio n. 11
0
 def __init__(self, ra0, dec0, r_c, beta, theta=0.0, ellipticity=1.0):
     r_c = parse_value(r_c, "arcsec")
     func = lambda r: (1.0 + (r / r_c)**2)**(-3 * beta + 0.5)
     super(BetaModel, self).__init__(ra0,
                                     dec0,
                                     func,
                                     theta=theta,
                                     ellipticity=ellipticity)
Esempio n. 12
0
def generate_sources(exp_time, fov, sky_center, area=40000.0, prng=None):
    r"""
    Make a catalog of point sources.

    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.
    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.
    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")
    area = parse_value(area, "cm**2")

    agn_fluxes, gal_fluxes = generate_fluxes(exp_time, area, fov, prng)

    fluxes = np.concatenate([agn_fluxes, gal_fluxes])

    ind = np.concatenate([
        get_agn_index(np.log10(agn_fluxes)),
        gal_index * np.ones(gal_fluxes.size)
    ])

    dec_scal = np.fabs(np.cos(sky_center[1] * np.pi / 180))
    ra_min = sky_center[0] - fov / (2.0 * 60.0 * dec_scal)
    dec_min = sky_center[1] - fov / (2.0 * 60.0)

    ra0 = prng.uniform(size=fluxes.size) * fov / (60.0 * dec_scal) + ra_min
    dec0 = prng.uniform(size=fluxes.size) * fov / 60.0 + dec_min

    return ra0, dec0, fluxes, ind
Esempio n. 13
0
    def new_spec_from_band(self, emin, emax):
        """
        Create a new :class:`~xcs_soxs.spectra.Spectrum` object
        from a subset of an existing one defined by a particular
        energy band.

        Parameters
        ----------
        emin : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`
            The minimum energy of the band in keV.
        emax : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`
            The maximum energy of the band in keV.
        """
        emin = parse_value(emin, "keV")
        emax = parse_value(emax, 'keV')
        band = np.logical_and(self.ebins.value >= emin,
                              self.ebins.value <= emax)
        idxs = np.where(band)[0]
        ebins = self.ebins.value[idxs]
        flux = self.flux.value[idxs[:-1]]
        return Spectrum(ebins, flux)
Esempio n. 14
0
    def add_absorption_line(self,
                            line_center,
                            line_width,
                            equiv_width,
                            line_type='gaussian'):
        """
        Add an absorption line to this spectrum.

        Parameters
        ----------
        line_center : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`
            The line center position in units of keV, in the observer frame.
        line_width : one or more float, (value, unit) tuple, or :class:`~astropy.units.Quantity`
            The line width (FWHM) in units of keV, in the observer frame. Can also
            input the line width in units of velocity in the rest frame. For the Voigt
            profile, a list, tuple, or array of two values should be provided since there
            are two line widths, the Lorentzian and the Gaussian (in that order).
        equiv_width : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`
            The equivalent width of the line, in units of milli-Angstrom
        line_type : string, optional
            The line profile type. Default: "gaussian"
        """
        line_center = parse_value(line_center, "keV")
        line_width = parse_value(line_width,
                                 "keV",
                                 equivalence=line_width_equiv(line_center))
        equiv_width = parse_value(equiv_width,
                                  "1.0e-3*angstrom")  # in milliangstroms
        equiv_width *= 1.0e-3  # convert to angstroms
        if line_type == "gaussian":
            sigma = line_width / sigma_to_fwhm
            B = equiv_width * line_center * line_center
            B /= hc * sqrt2pi * sigma
            f = Gaussian1D(B, line_center, sigma)
        else:
            raise NotImplementedError("Line profile type '%s' " % line_type +
                                      "not implemented!")
        self.flux *= np.exp(-f(self.emid.value))
        self._compute_total_flux()
Esempio n. 15
0
    def from_models(cls,
                    name,
                    spectral_model,
                    spatial_model,
                    t_exp,
                    area,
                    prng=None):
        """
        Generate a single photon list from a spectral and a spatial
        model. 

        Parameters
        ----------
        name : string
            The name of the photon list. This will also be the prefix 
            of any photon list file that is written from this photon list.
        spectral_model : :class:`~soxs.spectra.Spectrum`
            The spectral model to use to generate the event energies.
        spatial_model : :class:`~soxs.spatial.SpatialModel`
            The spatial model to use to generate the event coordinates.
        t_exp : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`
            The exposure time in seconds.
        area : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`
            The effective area in cm**2. If one is creating 
            events for a SIMPUT file, a constant should be 
            used and it must be large enough so that a 
            sufficiently large sample is drawn for the ARF.
        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)
        t_exp = parse_value(t_exp, "s")
        area = parse_value(area, "cm**2")
        e = spectral_model.generate_energies(t_exp, area, prng=prng)
        ra, dec = spatial_model.generate_coords(e.size, prng=prng)
        return cls(name, ra, dec, e, e.flux)
Esempio n. 16
0
    def rescale_flux(self,
                     new_flux,
                     emin=None,
                     emax=None,
                     flux_type="photons"):
        """
        Rescale the flux of the spectrum, optionally using 
        a specific energy band.

        Parameters
        ----------
        new_flux : float
            The new flux in units of photons/s/cm**2.
        emin : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`, optional
            The minimum energy of the band to consider, 
            in keV. Default: Use the minimum energy of 
            the entire spectrum.
        emax : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`, optional
            The maximum energy of the band to consider, 
            in keV. Default: Use the maximum energy of 
            the entire spectrum.
        flux_type : string, optional
            The units of the flux to use in the rescaling:
                "photons": photons/s/cm**2
                "energy": erg/s/cm**2
        """
        if emin is None:
            emin = self.ebins[0].value
        if emax is None:
            emax = self.ebins[-1].value
        emin = parse_value(emin, "keV")
        emax = parse_value(emax, 'keV')
        idxs = np.logical_and(self.emid.value >= emin, self.emid.value <= emax)
        if flux_type == "photons":
            f = self.flux[idxs].sum() * self.de
        elif flux_type == "energy":
            f = (self.flux * self.emid.to("erg"))[idxs].sum() * self.de
        self.flux *= new_flux / f.value
        self._compute_total_flux()
Esempio n. 17
0
    def from_constant(cls, const_flux, emin, emax, nbins):
        """
        Create a spectrum from a constant model using 
        XSPEC.

        Parameters
        ----------
        const_flux : float
            The value of the constant flux in the units 
            of the spectrum. 
        emin : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`
            The minimum energy of the spectrum in keV. 
        emax : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`
            The maximum energy of the spectrum in keV. 
        nbins : integer
            The number of bins in the spectrum.
        """
        emin = parse_value(emin, "keV")
        emax = parse_value(emax, 'keV')
        ebins = np.linspace(emin, emax, nbins + 1)
        flux = const_flux * np.ones(nbins)
        return cls(ebins, flux)
Esempio n. 18
0
def make_simple_instrument(base_inst,
                           new_inst,
                           fov,
                           num_pixels,
                           no_bkgnd=False,
                           no_psf=False,
                           no_dither=False):
    """
    Using an existing imaging instrument specification, 
    make a simple square instrument given a field of view 
    and a resolution.

    Parameters
    ----------
    base_inst : string
        The name for the instrument specification to base the 
        new one on.
    new_inst : string
        The name for the new instrument specification.
    fov : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`
        The field of view in arcminutes.
    num_pixels : integer
        The number of pixels on a side.
    no_bkgnd : boolean, optional
        Set this new instrument to have no particle background. 
        Default: False
    no_psf : boolean, optional
        Set this new instrument to have no spatial PSF. 
        Default: False
    no_dither : boolean, optional
        Set this new instrument to have no dithering. 
        Default: False
    """
    sq_inst = get_instrument_from_registry(base_inst)
    if sq_inst["imaging"] is False:
        raise RuntimeError("make_simple_instrument only works with "
                           "imaging instruments!")
    sq_inst["name"] = new_inst
    sq_inst["chips"] = None
    sq_inst["fov"] = parse_value(fov, "arcmin")
    sq_inst["num_pixels"] = num_pixels
    if no_bkgnd:
        sq_inst["bkgnd"] = None
    elif base_inst.startswith("aciss"):
        # Special-case ACIS-S to use the BI background on S3
        sq_inst["bkgnd"] = "aciss"
    if no_psf:
        sq_inst["psf"] = None
    if sq_inst["dither"]:
        sq_inst["dither"] = not no_dither
    add_instrument_to_registry(sq_inst)
Esempio n. 19
0
    def add_emission_line(self,
                          line_center,
                          line_width,
                          line_amp,
                          line_type="gaussian"):
        """
        Add an emission line to this spectrum.

        Parameters
        ----------
        line_center : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`
            The line center position in units of keV, in the observer frame.
        line_width : one or more float, (value, unit) tuple, or :class:`~astropy.units.Quantity`
            The line width (FWHM) in units of keV, in the observer frame. Can also
            input the line width in units of velocity in the rest frame. For the Voigt
            profile, a list, tuple, or array of two values should be provided since there
            are two line widths, the Lorentzian and the Gaussian (in that order).
        line_amp : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`
            The integrated line amplitude in the units of the flux 
        line_type : string, optional
            The line profile type. Default: "gaussian"
        """
        line_center = parse_value(line_center, "keV")
        line_width = parse_value(line_width,
                                 "keV",
                                 equivalence=line_width_equiv(line_center))
        line_amp = parse_value(line_amp, self._units)
        if line_type == "gaussian":
            sigma = line_width / sigma_to_fwhm
            line_amp /= sqrt2pi * sigma
            f = Gaussian1D(line_amp, line_center, sigma)
        else:
            raise NotImplementedError("Line profile type '%s' " % line_type +
                                      "not implemented!")
        self.flux += u.Quantity(f(self.emid.value), self._units)
        self._compute_total_flux()
Esempio n. 20
0
    def from_spectrum(cls, spec, fov):
        """
        Create a background spectrum from a regular
        :class:`~xcs_soxs.spectra.Spectrum` object and the width
        of a field of view on a side.

        Parameters
        ----------
        spec : :class:`~soxs.spectra.Spectrum`
            The spectrum to be used.
        fov : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`
            The width of the field of view on a side in 
            arcminutes.
        """
        fov = parse_value(fov, "arcmin")
        flux = spec.flux.value / fov / fov
        return cls(spec.flux.ebins.value, flux)
Esempio n. 21
0
def add_instrumental_background(name, filename, default_focal_length):
    """
    Add a particle/instrument background to the list 
    of known backgrounds.

    Parameters
    ----------
    name : string
        The short name of the background, which will 
        be the key in the registry.
    filename : string
        The file containing the background. It must 
        have two columns: energy in keV, and background 
        intensity in units of photons/s/cm**2/arcmin**2/keV.
    default_focal_length : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`
        The focal length of the telescope that this background
        is scaled to. Used for rescaling the background if an
        alternative focal length is provided in an instrument
        specification.
    """
    default_focal_length = parse_value(default_focal_length, "m")
    spec = InstrumentalBackgroundSpectrum.from_file(filename,
                                                    default_focal_length)
    instrument_backgrounds[name] = spec
Esempio n. 22
0
 def __init__(self, ra0, dec0, fov):
     fov = parse_value(fov, "arcmin")
     width = fov * 60.0
     height = fov * 60.0
     super(FillFOVModel, self).__init__(ra0, dec0, width, height)
Esempio n. 23
0
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
Esempio n. 24
0
 def to_spectrum(self, fov):
     fov = parse_value(fov, "arcmin")
     flux = self.flux.value * fov * fov
     return Spectrum(self.ebins.value, flux)
Esempio n. 25
0
 def __init__(self, ra0, dec0):
     self.ra0 = parse_value(ra0, "deg")
     self.dec0 = parse_value(dec0, "deg")
     self.w = construct_wcs(self.ra0, self.dec0)
Esempio n. 26
0
    def plot(self,
             center,
             width,
             s=None,
             c=None,
             marker=None,
             stride=1,
             emin=None,
             emax=None,
             label=None,
             fontsize=18,
             fig=None,
             ax=None,
             **kwargs):
        """
        Plot event coordinates from this photon list in a scatter plot, 
        optionally restricting the photon energies which are plotted
        and using only a subset of the photons. 

        Parameters
        ----------
        center : array-like
            The RA, Dec of the center of the plot in degrees.
        width : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`
            The width of the plot in arcminutes.
        s : integer, optional
            Size of the scatter marker in points^2.
        c : string, optional
            The color of the points.
        marker : string, optional
            The marker to use for the points in the scatter plot. Default: 'o'
        stride : integer, optional
            Plot every *stride* events. Default: 1
        emin : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`
            The minimum energy of the photons to plot. Default is
            the minimum energy in the list.
        emax : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`
            The maximum energy of the photons to plot. Default is
            the maximum energy in the list.
        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.
        """
        import matplotlib.pyplot as plt
        from astropy.visualization.wcsaxes import WCSAxes
        if fig is None:
            fig = plt.figure(figsize=(10, 10))
        if ax is None:
            wcs = construct_wcs(center[0], center[1])
            ax = WCSAxes(fig, [0.15, 0.1, 0.8, 0.8], wcs=wcs)
            fig.add_axes(ax)
        else:
            wcs = ax.wcs
        if emin is None:
            emin = self.energy.value.min()
        else:
            emin = parse_value(emin, "keV")
        if emax is None:
            emax = self.energy.value.max()
        else:
            emax = parse_value(emax, "keV")
        idxs = np.logical_and(self.energy.value >= emin,
                              self.energy.value <= emax)
        ra = self.ra[idxs][::stride].value
        dec = self.dec[idxs][::stride].value
        x, y = wcs.wcs_world2pix(ra, dec, 1)
        ax.scatter(x, y, s=s, c=c, marker=marker, label=label, **kwargs)
        x0, y0 = wcs.wcs_world2pix(center[0], center[1], 1)
        width = parse_value(width, "arcmin") * 60.0
        ax.set_xlim(x0 - 0.5 * width, x0 + 0.5 * width)
        ax.set_ylim(y0 - 0.5 * width, y0 + 0.5 * width)
        ax.set_xlabel("RA")
        ax.set_ylabel("Dec")
        ax.tick_params(axis='both', labelsize=fontsize)
        return fig, ax
Esempio n. 27
0
def write_image(evt_file,
                out_file,
                coord_type='sky',
                emin=None,
                emax=None,
                overwrite=False,
                expmap_file=None,
                reblock=1):
    r"""
    Generate a image by binning X-ray counts and write 
    it to a FITS file.

    Parameters
    ----------
    evt_file : string
        The name of the input event file to read.
    out_file : string
        The name of the image file to write.
    coord_type : string, optional
        The type of coordinate to bin into an image. 
        Can be "sky" or "det". Default: "sky"
    emin : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`, optional
        The minimum energy of the photons to put in the 
        image, in keV.
    emax : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`, optional
        The maximum energy of the photons to put in the 
        image, in keV.
    overwrite : boolean, optional
        Whether or not to overwrite an existing file with 
        the same name. Default: False
    expmap_file : string, optional
        Supply an exposure map file to divide this image by
        to get a flux map. Default: None
    reblock : integer, optional
        Change this value to reblock the image to larger 
        pixel sizes (reblock >= 1). Only supported for
        sky coordinates. Default: 1
    """
    if coord_type == "det" and reblock > 1:
        raise RuntimeError("Reblocking images is not supported "
                           "for detector coordinates!")
    f = pyfits.open(evt_file)
    e = f["EVENTS"].data["ENERGY"]
    if emin is None:
        emin = e.min()
    else:
        emin = parse_value(emin, "keV")
        emin *= 1000.
    if emax is None:
        emax = e.max()
    else:
        emax = parse_value(emax, "keV")
        emax *= 1000.
    idxs = np.logical_and(e > emin, e < emax)
    xcoord, ycoord, xcol, ycol = coord_types[coord_type]
    x = f["EVENTS"].data[xcoord][idxs]
    y = f["EVENTS"].data[ycoord][idxs]
    exp_time = f["EVENTS"].header["EXPOSURE"]
    xmin = f["EVENTS"].header["TLMIN%d" % xcol]
    ymin = f["EVENTS"].header["TLMIN%d" % ycol]
    xmax = f["EVENTS"].header["TLMAX%d" % xcol]
    ymax = f["EVENTS"].header["TLMAX%d" % ycol]
    if coord_type == 'sky':
        xctr = f["EVENTS"].header["TCRVL%d" % xcol]
        yctr = f["EVENTS"].header["TCRVL%d" % ycol]
        xdel = f["EVENTS"].header["TCDLT%d" % xcol] * reblock
        ydel = f["EVENTS"].header["TCDLT%d" % ycol] * reblock
    f.close()

    nx = int(xmax - xmin) // reblock
    ny = int(ymax - ymin) // reblock

    xbins = np.linspace(xmin, xmax, nx + 1, endpoint=True)
    ybins = np.linspace(ymin, ymax, ny + 1, endpoint=True)

    H, xedges, yedges = np.histogram2d(x, y, bins=[xbins, ybins])

    if expmap_file is not None:
        if coord_type == "det":
            raise RuntimeError("Cannot divide by an exposure map for images "
                               "binned in detector coordinates!")
        f = pyfits.open(expmap_file)
        if f["EXPMAP"].shape != (nx, ny):
            raise RuntimeError(
                "Exposure map and image do not have the same shape!!")
        with np.errstate(invalid='ignore', divide='ignore'):
            H /= f["EXPMAP"].data.T
        H[np.isinf(H)] = 0.0
        H = np.nan_to_num(H)
        H[H < 0.0] = 0.0
        f.close()

    hdu = pyfits.PrimaryHDU(H.T)

    if coord_type == 'sky':
        hdu.header["MTYPE1"] = "EQPOS"
        hdu.header["MFORM1"] = "RA,DEC"
        hdu.header["CTYPE1"] = "RA---TAN"
        hdu.header["CTYPE2"] = "DEC--TAN"
        hdu.header["CRVAL1"] = xctr
        hdu.header["CRVAL2"] = yctr
        hdu.header["CUNIT1"] = "deg"
        hdu.header["CUNIT2"] = "deg"
        hdu.header["CDELT1"] = xdel
        hdu.header["CDELT2"] = ydel
        hdu.header["CRPIX1"] = 0.5 * (nx + 1)
        hdu.header["CRPIX2"] = 0.5 * (ny + 1)
    else:
        hdu.header["CUNIT1"] = "pixel"
        hdu.header["CUNIT2"] = "pixel"

    hdu.header["EXPOSURE"] = exp_time

    hdu.writeto(out_file, overwrite=overwrite)
Esempio n. 28
0
def write_radial_profile(evt_file,
                         out_file,
                         ctr,
                         rmin,
                         rmax,
                         nbins,
                         ctr_type="celestial",
                         emin=None,
                         emax=None,
                         expmap_file=None,
                         overwrite=False):
    r"""
    Bin up events into a radial profile and write them to a FITS
    table. 

    Parameters
    ----------
    evt_file : string
        Input event file.
    out_file : string
        The output file to write the profile to. 
    ctr : array-like
        The central coordinate of the profile. Can either be in 
        celestial coordinates (the default) or "physical" pixel 
        coordinates. If the former, the ``ctr_type`` keyword 
        argument must be explicity set to "physical".
    rmin : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`
        The minimum radius of the profile, in arcseconds. 
    rmax : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`
        The maximum radius of the profile, in arcseconds.
    nbins : integer
        The number of bins in the profile.
    ctr_type : string, optional
        The type of center coordinate. Either "celestial" for 
        (RA, Dec) coordinates (the default), or "physical" for 
        pixel coordinates.
    emin : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`, optional
        The minimum energy of the events to be binned in keV. 
        Default is the lowest energy available.
    emax : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`, optional
        The maximum energy of the events to be binned in keV. 
        Default is the highest energy available.
    overwrite : boolean, optional
        Whether or not to overwrite an existing file with the 
        same name. Default: False
    expmap_file : string, optional
        Supply an exposure map file to determine fluxes. 
        Default: None
    """
    import astropy.wcs as pywcs
    rmin = parse_value(rmin, "arcsec")
    rmax = parse_value(rmax, "arcsec")
    f = pyfits.open(evt_file)
    hdu = f["EVENTS"]
    orig_dx = hdu.header["TCDLT3"]
    e = hdu.data["ENERGY"]
    if emin is None:
        emin = e.min()
    else:
        emin = parse_value(emin, "keV")
        emin *= 1000.
    if emax is None:
        emax = e.max()
    else:
        emax = parse_value(emax, "keV")
        emax *= 1000.
    idxs = np.logical_and(e > emin, e < emax)
    x = hdu.data["X"][idxs]
    y = hdu.data["Y"][idxs]
    exp_time = hdu.header["EXPOSURE"]
    w = wcs_from_event_file(f)
    dtheta = np.abs(w.wcs.cdelt[1]) * 3600.0
    f.close()

    if ctr_type == "celestial":
        ctr = w.all_world2pix(ctr[0], ctr[1], 1)

    r = np.sqrt((x - ctr[0])**2 + (y - ctr[1])**2)
    rr = np.linspace(rmin / dtheta, rmax / dtheta, nbins + 1)
    C = np.histogram(r, bins=rr)[0]
    rbin = rr * dtheta
    rmid = 0.5 * (rbin[1:] + rbin[:-1])

    A = np.pi * (rbin[1:]**2 - rbin[:-1]**2)

    Cerr = np.sqrt(C)

    R = C / exp_time
    Rerr = Cerr / exp_time

    S = R / A
    Serr = Rerr / A

    col1 = pyfits.Column(name='RLO',
                         format='D',
                         unit='arcsec',
                         array=rbin[:-1])
    col2 = pyfits.Column(name='RHI', format='D', unit='arcsec', array=rbin[1:])
    col3 = pyfits.Column(name='RMID', format='D', unit='arcsec', array=rmid)
    col4 = pyfits.Column(name='AREA', format='D', unit='arcsec**2', array=A)
    col5 = pyfits.Column(name='NET_COUNTS', format='D', unit='count', array=C)
    col6 = pyfits.Column(name='NET_ERR', format='D', unit='count', array=Cerr)
    col7 = pyfits.Column(name='NET_RATE', format='D', unit='count/s', array=R)
    col8 = pyfits.Column(name='ERR_RATE',
                         format='D',
                         unit='count/s',
                         array=Rerr)
    col9 = pyfits.Column(name='SUR_BRI',
                         format='D',
                         unit='count/s/arcsec**2',
                         array=S)
    col10 = pyfits.Column(name='SUR_BRI_ERR',
                          format='1D',
                          unit='count/s/arcsec**2',
                          array=Serr)

    coldefs = [col1, col2, col3, col4, col5, col6, col7, col8, col9, col10]

    if expmap_file is not None:
        f = pyfits.open(expmap_file)
        ehdu = f["EXPMAP"]
        wexp = pywcs.WCS(header=ehdu.header)
        cel = w.all_pix2world(ctr[0], ctr[1], 1)
        ectr = wexp.all_world2pix(cel[0], cel[1], 1)
        exp = ehdu.data[:, :]
        nx, ny = exp.shape
        reblock = ehdu.header["CDELT2"] / orig_dx
        x, y = np.mgrid[1:nx + 1, 1:ny + 1]
        r = np.sqrt((x - ectr[0])**2 + (y - ectr[1])**2)
        f.close()
        E = np.histogram(r, bins=rr / reblock, weights=exp)[0] / np.histogram(
            r, bins=rr / reblock)[0]
        with np.errstate(invalid='ignore', divide='ignore'):
            F = R / E
            Ferr = Rerr / E
        SF = F / A
        SFerr = Ferr / A
        col11 = pyfits.Column(name='MEAN_SRC_EXP',
                              format='D',
                              unit='cm**2',
                              array=E)
        col12 = pyfits.Column(name='NET_FLUX',
                              format='D',
                              unit='count/s/cm**2',
                              array=F)
        col13 = pyfits.Column(name='NET_FLUX_ERR',
                              format='D',
                              unit='count/s/cm**2',
                              array=Ferr)
        col14 = pyfits.Column(name='SUR_FLUX',
                              format='D',
                              unit='count/s/cm**2/arcsec**2',
                              array=SF)
        col15 = pyfits.Column(name='SUR_FLUX_ERR',
                              format='D',
                              unit='count/s/cm**2/arcsec**2',
                              array=SFerr)
        coldefs += [col11, col12, col13, col14, col15]

    tbhdu = pyfits.BinTableHDU.from_columns(pyfits.ColDefs(coldefs))
    tbhdu.name = "PROFILE"

    hdulist = pyfits.HDUList([pyfits.PrimaryHDU(), tbhdu])

    hdulist.writeto(out_file, overwrite=overwrite)
Esempio n. 29
0
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 xcs_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.")
Esempio n. 30
0
 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:]