예제 #1
0
파일: spectra.py 프로젝트: eblur/soxs
    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
예제 #2
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
예제 #3
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)
예제 #4
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()
예제 #5
0
파일: simput.py 프로젝트: jzuhone/xrs_utils
    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)
예제 #6
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()
예제 #7
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()
예제 #8
0
파일: spectra.py 프로젝트: eblur/soxs
    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()
예제 #9
0
파일: spectra.py 프로젝트: NegriAndrea/soxs
    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()
예제 #10
0
def generate_fluxes(exp_time, area, fov, prng):
    from 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
예제 #11
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
예제 #12
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
예제 #13
0
파일: spectra.py 프로젝트: eblur/soxs
    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
예제 #14
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)
예제 #15
0
파일: spectra.py 프로젝트: eblur/soxs
 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)
예제 #16
0
파일: spectra.py 프로젝트: eblur/soxs
    def from_powerlaw(cls, photon_index, redshift, norm,
                      emin=0.01, emax=50.0, nbins=10000):
        """
        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`, optional
            The minimum energy of the spectrum in keV. 
            Default: 0.01
        emax : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`, optional
            The maximum energy of the spectrum in keV. 
            Default: 50.0
        nbins : integer, optional
            The number of bins in the spectrum. Default: 10000
        """
        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)
예제 #17
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
예제 #18
0
파일: spectra.py 프로젝트: NegriAndrea/soxs
 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
예제 #19
0
    def __init__(self,
                 ra0,
                 dec0,
                 r_in,
                 r_out,
                 num_events,
                 theta=0.0,
                 ellipticity=1.0,
                 prng=None):
        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,
                                           num_events,
                                           theta=theta,
                                           ellipticity=ellipticity,
                                           prng=prng)
예제 #20
0
파일: spectra.py 프로젝트: NegriAndrea/soxs
    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()
예제 #21
0
 def __init__(self, ra0, dec0, num_events):
     ra0 = parse_value(ra0, "deg")
     dec0 = parse_value(dec0, "deg")
     ra = ra0 * np.ones(num_events)
     dec = dec0 * np.ones(num_events)
     w = construct_wcs(ra0, dec0)
     zero_pos = np.zeros(num_events)
     super(PointSourceModel, self).__init__(ra, dec, zero_pos, zero_pos, w)
예제 #22
0
파일: spatial.py 프로젝트: NegriAndrea/soxs
 def __init__(self, ra0, dec0, r_c1, beta1, r_c2, beta2, sb_ratio,
              theta=0.0, ellipticity=1.0):
     r_c1 = parse_value(r_c1, "arcsec")
     r_c2 = parse_value(r_c2, "arcsec")
     func = lambda r: (1.0+(r/r_c1)**2)**(-3*beta1+0.5) + \
                      sb_ratio*(1.0+(r/r_c2)**2)**(-3*beta2+0.5)
     super(DoubleBetaModel, self).__init__(ra0, dec0, func, theta=theta,
                                           ellipticity=ellipticity)
예제 #23
0
파일: spectra.py 프로젝트: NegriAndrea/soxs
 def _new_spec_from_band(self, emin, emax):
     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 ebins, flux
예제 #24
0
 def to_scaled_spectrum(self, fov, focal_length=None):
     from 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)
예제 #25
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)
예제 #26
0
 def to_scaled_spectrum(self, fov, focal_length=None):
     from 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)
예제 #27
0
파일: spectra.py 프로젝트: NegriAndrea/soxs
    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()
예제 #28
0
 def convolve_spectrum(self, cspec, exp_time, noisy=True, prng=None):
     prng = parse_prng(prng)
     exp_time = parse_value(exp_time, "s")
     counts = cspec.flux.value * exp_time * cspec.de.value
     spec = np.histogram(cspec.emid.value, self.ebins, weights=counts)[0]
     conv_spec = np.zeros(self.n_ch)
     pbar = tqdm(leave=True, total=self.n_e, desc="Convolving spectrum ")
     if np.all(self.data["N_GRP"] == 1):
         # We can do things a bit faster if there is only one group each
         f_chan = ensure_numpy_array(np.nan_to_num(self.data["F_CHAN"]))
         n_chan = ensure_numpy_array(np.nan_to_num(self.data["N_CHAN"]))
         mat = np.nan_to_num(np.float64(self.data["MATRIX"]))
         mat_size = np.minimum(n_chan, self.n_ch-f_chan)
         for k in range(self.n_e):
             conv_spec[f_chan[k]:f_chan[k]+n_chan[k]] += spec[k]*mat[k,:mat_size[k]]
             pbar.update()
     else:
         # Otherwise, we have to go step-by-step
         for k in range(self.n_e):
             f_chan = ensure_numpy_array(np.nan_to_num(self.data["F_CHAN"][k]))
             n_chan = ensure_numpy_array(np.nan_to_num(self.data["N_CHAN"][k]))
             mat = np.nan_to_num(np.float64(self.data["MATRIX"][k]))
             mat_size = np.minimum(n_chan, self.n_ch-f_chan)
             for i, f in enumerate(f_chan):
                 conv_spec[f:f+n_chan[i]] += spec[k]*mat[:mat_size[i]]
             pbar.update()
     pbar.close()
     if noisy:
         return prng.poisson(lam=conv_spec)
     else:
         return conv_spec
예제 #29
0
def generate_sources(fov, sky_center, prng=None):
    r"""
    Make a catalog of point sources.

    Parameters
    ----------
    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.
    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)

    fov = parse_value(fov, "arcmin")

    agn_fluxes, gal_fluxes = generate_fluxes(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)
    ])

    ra0, dec0 = generate_positions(fluxes.size, fov, sky_center, prng)

    return ra0, dec0, fluxes, ind
예제 #30
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()
예제 #31
0
def generate_fluxes(fov, prng):
    from soxs.data import cdf_fluxes, cdf_gal, cdf_agn
    prng = parse_prng(prng)

    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)

    fov_area = fov**2

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

    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
예제 #32
0
파일: spectra.py 프로젝트: eblur/soxs
    def get_spectrum(self, kT, abund, redshift, norm, velocity=0.0,
                     elem_abund=None):
        """
        Get a thermal emission spectrum.

        Parameters
        ----------
        kT : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`
            The temperature in keV.
        abund : float
            The metal abundance in solar units. 
        redshift : float
            The redshift.
        norm : float
            The normalization of the model, in the standard
            Xspec units of 1.0e-14*EM/(4*pi*(1+z)**2*D_A**2).
        velocity : float, (value, unit) tuple, or :class:`~astropy.units.Quantity`, optional
            The velocity broadening parameter, in units of 
            km/s. Default: 0.0
        elem_abund : dict of element name, float pairs
            A dictionary of elemental abundances to vary
            freely of the abund parameter. Default: None
        """
        kT = parse_value(kT, "keV")
        velocity = parse_value(velocity, "km/s")
        v = velocity*1.0e5
        if elem_abund is None:
            elem_abund = {}
        if set(elem_abund.keys()) != set(self.var_elem_names):
            raise RuntimeError("The supplied set of abundances is not the "
                               "same as that was originally set!\n"
                               "Free elements: %s\nAbundances: %s" % (set(elem_abund.keys()),
                                                                      set(self.var_elem_names)))
        tindex = np.searchsorted(self.Tvals, kT)-1
        if tindex >= self.Tvals.shape[0]-1 or tindex < 0:
            return np.zeros(self.nbins)
        dT = (kT-self.Tvals[tindex])/self.dTvals[tindex]
        cspec, mspec, vspec = self._get_table([tindex, tindex+1], redshift, v)
        cosmic_spec = cspec[0,:]*(1.-dT)+cspec[1,:]*dT
        metal_spec = mspec[0,:]*(1.-dT)+mspec[1,:]*dT
        spec = cosmic_spec + abund*metal_spec
        if vspec is not None:
            for elem, eabund in elem_abund.items():
                j = self.var_elem_names.index(elem)
                spec += eabund*(vspec[j,0,:]*(1.-dT)+vspec[j,1,:]*dT)
        spec = 1.0e14*norm*spec/self.de
        return Spectrum(self.ebins, spec)
예제 #33
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)
예제 #34
0
파일: spectra.py 프로젝트: eblur/soxs
    def __init__(self, emin, emax, nbins, var_elem=None, apec_root=None,
                 apec_vers="3.0.8", broadening=True, nolines=False):
        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
        self.cocofile = os.path.join(apec_root, "apec_v%s_coco.fits" % apec_vers)
        self.linefile = os.path.join(apec_root, "apec_v%s_line.fits" % apec_vers)
        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))
        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("COCO 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()
        if var_elem is None:
            self.var_elem = []
        else:
            self.var_elem = [elem_names.index(elem) for elem in var_elem]
        self.var_elem.sort()
        self.var_elem_names = [elem_names[elem] for elem in self.var_elem]
        self.num_var_elem = len(self.var_elem)
        self.cosmic_elem = [elem for elem in cosmic_elem 
                            if elem not in self.var_elem]
        self.metal_elem = [elem for elem in metal_elem
                           if elem not in self.var_elem]
예제 #35
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
예제 #36
0
 def __init__(self, ra0, dec0, fov, num_events, prng=None):
     fov = parse_value(fov, "arcmin")
     width = fov * 60.0
     height = fov * 60.0
     super(FillFOVModel, self).__init__(ra0,
                                        dec0,
                                        width,
                                        height,
                                        num_events,
                                        prng=prng)
예제 #37
0
 def __init__(self,
              ra0,
              dec0,
              width,
              height,
              num_events,
              theta=0.0,
              prng=None):
     prng = parse_prng(prng)
     ra0 = parse_value(ra0, "deg")
     dec0 = parse_value(dec0, "deg")
     width = parse_value(width, "arcsec")
     height = parse_value(height, "arcsec")
     w = construct_wcs(ra0, dec0)
     x = prng.uniform(low=-0.5 * width, high=0.5 * width, size=num_events)
     y = prng.uniform(low=-0.5 * height, high=0.5 * height, size=num_events)
     coords = rotate_xy(theta, x, y)
     ra, dec = w.wcs_pix2world(coords[0, :], coords[1, :], 1)
     super(RectangleModel, self).__init__(ra, dec, coords[0, :],
                                          coords[1, :], w)
예제 #38
0
    def new_spec_from_band(self, emin, emax):
        """
        Create a new :class:`~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)
예제 #39
0
 def __init__(self,
              ra0,
              dec0,
              func,
              num_events,
              theta=0.0,
              ellipticity=1.0,
              prng=None):
     prng = parse_prng(prng)
     ra0 = parse_value(ra0, "deg")
     dec0 = parse_value(dec0, "deg")
     theta = parse_value(theta, "deg")
     x, y = generate_radial_events(num_events,
                                   func,
                                   prng,
                                   ellipticity=ellipticity)
     w = construct_wcs(ra0, dec0)
     coords = rotate_xy(theta, x, y)
     ra, dec = w.wcs_pix2world(coords[0, :], coords[1, :], 1)
     super(RadialFunctionModel, self).__init__(ra, dec, coords[0, :],
                                               coords[1, :], w)
예제 #40
0
파일: spectra.py 프로젝트: eblur/soxs
    def new_spec_from_band(self, emin, emax):
        """
        Create a new :class:`~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)
예제 #41
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)
예제 #42
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)
예제 #43
0
파일: spectra.py 프로젝트: NegriAndrea/soxs
    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)
예제 #44
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)
예제 #45
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
예제 #46
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)
예제 #47
0
 def convolve_spectrum(self, cspec, exp_time, prng=None):
     prng = parse_prng(prng)
     exp_time = parse_value(exp_time, "s")
     counts = cspec.flux.value * exp_time * cspec.de.value
     spec = np.histogram(cspec.emid.value, self.ebins, weights=counts)[0]
     conv_spec = np.zeros(self.n_ch)
     pbar = tqdm(leave=True, total=self.n_e, desc="Convolving spectrum ")
     for k in range(self.n_e):
         f_chan = ensure_numpy_array(np.nan_to_num(self.data["F_CHAN"][k]))
         n_chan = ensure_numpy_array(np.nan_to_num(self.data["N_CHAN"][k]))
         mat = np.nan_to_num(np.float64(self.data["MATRIX"][k]))
         for f, n in zip(f_chan, n_chan):
             mat_size = min(n, self.n_ch-f)
             conv_spec[f:f+n] += spec[k]*mat[:mat_size]
         pbar.update()
     pbar.close()
     return prng.poisson(lam=conv_spec)
예제 #48
0
    def from_spectrum(cls, spec, fov):
        """
        Create a background spectrum from a regular
        :class:`~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)
예제 #49
0
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)
예제 #50
0
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
예제 #51
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("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:]
예제 #52
0
 def __init__(self, ra0, dec0, func, theta=0.0, ellipticity=1.0):
     super(RadialFunctionModel, self).__init__(ra0, dec0)
     self.theta = parse_value(theta, "deg")
     self.func = func
     self.ellipticity = ellipticity
예제 #53
0
 def __init__(self, ra0, dec0, width, height, theta=0.0):
     super(RectangleModel, self).__init__(ra0, dec0)
     self.width = parse_value(width, "arcsec")
     self.height = parse_value(height, "arcsec")
     self.theta = parse_value(theta, "deg")
예제 #54
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)
예제 #55
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)
예제 #56
0
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
예제 #57
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)
예제 #58
0
 def e_to_ch(self, energy):
     energy = parse_value(energy, "keV")
     return np.searchsorted(self.ebounds_data["E_MIN"], energy)-1