示例#1
0
def load_skyspect(fn = r'T:\data\galprop\ring_21month_P6v11.fits', 
# r'D:\fermi\data\galprop\gll_iem_v02.fit', 
        nside=192, 
        show_kw = dict(fun=np.log10, cmap='hot'),
        ):
    """
    load a galactic diffuse distribution.
    Save the HEALpix respresentation at an energy (1 GeV default)
    
    fn : string
        filename for the FITS representaion of a  SKySpectrum  
    nside: int
        HEALpix nside to use for represenation -- note that 192 is 12*16, about 0.25 deg
    show_kw : dict
        fun: weighting function, cmap, vmin, vmax
    """
    t = SkyImage(fn)
    galname = os.path.split(fn)[-1]
    print '%s: nx, ny, layers: %d %d %d' %(galname, t.naxis1(), t.naxis2(), t.layers())
    hpdir = Band(nside).dir
    dmap = map(lambda i:t(hpdir(i)), xrange(12*nside**2))
    tdm=DisplayMap(dmap)
    tdm.fill_ait(fignum=12, source_kw=dict(edgecolor='w',), show_kw=show_kw )
    plt.title(galname+' (1 GeV)')
    sfn = galname.split('.')[0]+'.png'
    plt.savefig(galname.split('.')[0]+'.png', bbox_inches='tight', pad_inches=0)
    print 'saved figure to %s' % sfn
    return tdm
示例#2
0
class ROIImage(object):
    """ This object is suitable for creating a SkyImage object
        and filling it with some physically meaningful
        quantity gotten from an ROIAnalysis object. 
        The acutal work is done by subclasses. """

    defaults = (
        ('size', 2, 'size of image in degrees'),
        ('pixelsize', 0.1, 'size, in degrees, of pixels'),
        ('galactic', False, 'galactic or equatorial coordinates'),
        ('proj', 'ZEA', 'projection name: can change if desired'),
        ('center', None, 'Center of image. If None, use roi center.'),
        ('conv_type', -1, 'Conversion type'),
    )

    @keyword_options.decorate(defaults)
    def __init__(self, roi, **kwargs):
        """ Note, unlike ZEA, can support non-square images. To specify a nonsquare
            image, set the size parameter to a lenght two tuple:
                
                size=(10,5) # dx=10 degrees, dy=5 degrees. 
        """
        keyword_options.process(self, kwargs)

        if self.size < self.pixelsize:
            raise Exception("Can only create images with >=1 pixel in them.")

        self.roi = roi

        self.selected_bands = tuple(self.roi.bands if self.conv_type < 0 else \
            [ band for band in self.roi.bands if band.ct == self.conv_type ])

        # by default, use get energy range and image center from roi.
        if self.center is None: self.center = self.roi.roi_dir

        # set up, then create a SkyImage object to perform the projection
        # to a grid and manage an image
        if not isinstance(self.size, collections.Iterable):

            # make sure size and pixelsize are commensurate (helpful for
            # various downsampling code later).
            self.size = int(self.size / self.pixelsize + 0.01) * self.pixelsize
            self.skyimage = SkyImage(self.center, '', self.pixelsize,
                                     self.size, 1, self.proj, self.galactic,
                                     False)
        else:
            self.skyimage = SkyImage(self.center, '', self.pixelsize,
                                     float(self.size[0]), 1, self.proj,
                                     self.galactic, False, float(self.size[1]))

        self.fill()

        self.nx, self.ny = self.skyimage.naxis1(), self.skyimage.naxis2()
        self.image = ROIImage.skyimage2numpy(self.skyimage)

    @staticmethod
    def skyimage2numpy(skyimage):
        nx, ny = skyimage.naxis1(), skyimage.naxis2()
        image = np.array(skyimage.image()).reshape((ny, nx))
        return image

    @abstractmethod
    def fill(self):
        pass

    def get_ZEA(self, axes=None, nticks=None):
        """ axes and nticks can be created by this object's constructor, but are
            more logically specified here. If they are not specified, get values from
            initial object creation. """
        # get out of the object all parameters which should be passed to ZEA.

        if hasattr(self.size, '__iter__'):
            raise Exception("Can only create ZEA object for square objects.")

        zea_dict = dict((d[0],self.__dict__[d[0]]) for d in ZEA.defaults if hasattr(d,'__iter__') and \
                hasattr(self,d[0]))
        if axes is not None: zea_dict['axes'] = axes
        if nticks is not None: zea_dict['nticks'] = nticks

        from uw.utilities.image import ZEA
        zea = ZEA(self.center, **zea_dict)
        zea.skyimage = self.skyimage
        # recalculate, in case the sky image has changed
        zea.image = ROIImage.skyimage2numpy(self.skyimage)

        # The old one gets removed by python's garbage collector (when zea.skyimage is replaced).
        zea.projector = zea.skyimage.projector()

        zea.vmin, zea.vmax = zea.skyimage.minimum(), zea.skyimage.maximum()
        return zea

    def get_pyfits(self):
        """ Create and return a pyfits object that corresponds to the ROIImage object. 
            The fits file created is supposed to be consistent with the internal
            representation that SkyImage/SkyProj uses. """

        if self.galactic:
            ctype1 = "GLON-%s" % self.proj
            ctype2 = "GLAT-%s" % self.proj
            # for some reason, SkyDir(0,0,SkyDir.GALACTIC).l() = 360
            crval1, crval2 = self.center.l() % 360, self.center.b()
        else:
            ctype1 = "RA-%s" % self.proj
            ctype2 = "DEC-%s" % self.proj
            crval1, crval2 = self.center.ra(), self.center.dec()

        cdelt1, cdelt2 = -self.pixelsize, self.pixelsize

        # from SkyImage.cxx like 92:
        #   "center pixel; WCS convention is that center of a pixel is a half-integer"
        crpix1, crpix2 = (self.skyimage.naxis1() +
                          1) / 2.0, (self.skyimage.naxis2() + 1) / 2.0

        values = [
            ["TELESCOP", "GLAST"],
            ["INSTRUME", "LAT"],
            ["DATE-OBS", ""],
            ["DATE-END", ""],
            ["EQUINOX", 2000.0, "Equinox of RA & DEC specifications"],
            [
                "CTYPE1", ctype1,
                "[RA|GLON]---%%%, %%% represents the projection method such as AIT"
            ],
            ["CRPIX1", crpix1, "Reference pixel"],
            ["CRVAL1", crval1, "RA or GLON at the reference pixel"],
            [
                "CDELT1", cdelt1,
                "X-axis incr per pixel of physical coord at position of ref pixel(deg)"
            ],
            [
                "CTYPE2", ctype2,
                "[DEC|GLAT]---%%%, %%% represents the projection method such as AIT"
            ],
            ["CRPIX2", crpix2, "Reference pixel"],
            ["CRVAL2", crval2, "DEC or GLAT at the reference pixel"],
            [
                "CDELT2", cdelt2,
                "Y-axis incr per pixel of physical coord at position of ref pixel(deg)"
            ],
            ["CROTA2", 0, "Image rotation (deg)"],
        ]
        for i in values:
            if len(i) > 2 and len(i[2]) > 47: i[2] = i[2][0:47]

        cards = [pyfits.Card(*i) for i in values]

        header = pyfits.Header(cards=cards)

        hdu = pyfits.PrimaryHDU(data=self.image, header=header)
        fits = pyfits.HDUList([hdu])

        return fits
示例#3
0
class ModelImage(ROIImage):
    """ This ROIImage subclass fills the sky image with the model
        predicted counts for a fermi sky model described by an ROIAnalysis
        object.

        This code is forced to deal with the fact that model intensity
        can vary significantly across a spatial pixel. The rest of the
        pointlike code can avoid this whole issue by scaling the healpix
        pixel size with the PSF to ensure that pixels are always small
        compared to the instrument's intrisic resolution. But since
        model predicted counts maps can be generated of arbitary pixel size,
        this issue must directly be dealt with.

        The solution that this code uses to deal with this issue is to
        simply sample from a grid finer by an integer number of pixels
        in each dimensions.  After calculating the model predictions,
        the nearby blocks of model predictions are averaged to downsample
        to create the model predictions.  This formulation assumes that
        each of the subpixels has the same solid angle and so it only
        suitable for relativly small images where pixels have equal area.
        For that reason, it is advised to use the ZEA projection.

        For point and extended sources, the characteristic scale with
        which the convolution must be small compared to is the PSF. So
        the formula for determining the factor is

        factor = ceil(pixelsize/r10)

        Where pxielsize is the plotting pixel size and r10 is the 10%
        containment radius of the PSF.

        For background sources, the characteristic scale is not the PSF
        but the convolution grid pixelsize. So the formula for determining
        the factor is instead
        
        factor = ceil(pixelsize/(conv_pixelsize/4))

        Where conv_pixelsize is the size of the convolution grid's pixels.
        
        For background sources, this algorithm is generally efficiency
        since we except the background to vary on this smaller scale all
        across the image.  But for point and (small) extended sources,
        this algorithm is generally very poor because it requires
        calculating the PSF (or PDF) at many points where the value
        is very close to 0. A better algorithm would be an adaptive
        quadrature integration algorithm which evaluate the integral in
        each pixel, then did a more accurate integral and iterated until
        the integral converged. This would avoid having to evaluate the
        model predictions for a source very finely far from the source.
        On the other hand, adding this feature (presumably to C++
        for optimization) would be very costly, and this code runs
        fast enough...

        """

    defaults = ROIImage.defaults + (
        ('override_point_sources', None,
         """ If either is specified, use override_point_sources these
                                                 and override_diffuse_sources to generate the image instead
                                                 of the sources in the ROI."""
         ),
        ('override_diffuse_sources', None, 'Same as override_point_sources'),
    )

    @keyword_options.decorate(defaults)
    def __init__(self, *args, **kwargs):
        if kwargs.has_key('proj') and kwargs['proj'] != 'ZEA':
            print "Warning, it is strongly advised to use the 'ZEA projection when creating model counts maps."

        super(ModelImage, self).__init__(*args, **kwargs)

    def fill(self):
        self.wsdl = self.skyimage.get_wsdl()

        self.solid_angle = np.radians(self.pixelsize)**2

        model_counts = np.zeros(len(self.wsdl), dtype=float)

        model_counts += self.all_point_source_counts()
        model_counts += self.all_diffuse_sources_counts()
        model_counts *= self.roi.phase_factor  # don't forget about the phase factor!
        #NB -- this will need to be fixed if want to account for bracketing IRFs

        PythonUtilities.set_wsdl_weights(model_counts, self.wsdl)

        self.skyimage.set_wsdl(self.wsdl)

    @staticmethod
    def downsample(myarr, factor):
        """
        Code taken from http://code.google.com/p/agpy/source/browse/trunk/agpy/downsample.py

        Downsample a 1D or 2D array by averaging over *factor* pixels in each axis.
        Crops upper edge if the shape is not a multiple of factor.

        This code is pure numpy and should be fast.
        """
        assert isinstance(factor, numbers.Integral)
        assert len(myarr.shape) <= 2

        if len(myarr.shape) == 1:
            xs = myarr.shape[0]
            assert xs % factor == 0
            dsarr = np.concatenate([[myarr[i::factor]]
                                    for i in range(factor)]).mean(axis=0)
            return dsarr

        elif len(myarr.shape) == 2:
            xs, ys = myarr.shape
            assert xs % factor == 0 and ys % factor == 0
            dsarr = np.concatenate(
                [[myarr[i::factor, j::factor] for i in range(factor)]
                 for j in range(factor)]).mean(axis=0)
            return dsarr

    def bigger_wsdl(self, band, compare=None):
        """ Want to sample on a grid that is comparable in size (or
            smaller than) 10% of the psf to ensure we get a reasonable
            sampling of the grid. """
        if compare is None:
            r10 = band.psf.inverse_integral_on_axis(0.10)
            compare = r10

        self.factor = int(np.ceil(self.pixelsize / compare))

        if self.factor == 1:
            return self.wsdl
        else:
            # hold onto this thing since it is needed by downsample_model
            if not hasattr(self.size, '__iter__'):
                self.fine_skyimage = SkyImage(
                    self.center, '',
                    float(self.pixelsize) / self.factor, self.size, 1,
                    self.proj, self.galactic, False)
            else:
                self.fine_skyimage = SkyImage(
                    self.center, '',
                    float(self.pixelsize) / self.factor, float(self.size[0]),
                    1, self.proj, self.galactic, False, float(self.size[1]))

            wsdl = self.fine_skyimage.get_wsdl()
            return wsdl

    def downsample_model(self, rvals):
        if self.factor == 1:
            return rvals
        else:
            rvals = rvals.reshape(
                (self.fine_skyimage.naxis2(), self.fine_skyimage.naxis1()))
            rvals = ModelImage.downsample(rvals, self.factor).flatten()
            return rvals

    @staticmethod
    def get_point_sources(roi, override_point_sources,
                          override_diffuse_sources):
        if override_point_sources is None and override_diffuse_sources is None:
            return roi.psm.point_sources
        if override_point_sources is None:
            return []
        elif not isinstance(override_point_sources, collections.Iterable):
            return [override_point_sources]
        else:
            return override_point_sources

    def all_point_source_counts(self):
        """ Calculate the point source contributions. """
        point_sources = ModelImage.get_point_sources(
            self.roi, self.override_point_sources,
            self.override_diffuse_sources)
        if len(point_sources) == 0: return 0

        point_counts = np.zeros(len(self.wsdl), dtype=float)

        for band in self.selected_bands:
            cpsf = band.psf.cpsf

            # generate a list of skydirs on a finer grid.
            wsdl = self.bigger_wsdl(band)

            rvals = np.empty(len(wsdl), dtype=float)

            for nps, ps in enumerate(point_sources):
                # evaluate the PSF at the center of each pixel
                cpsf.wsdl_val(rvals, ps.skydir, wsdl)

                # average the finer grid back to original resolution.
                temp = self.downsample_model(rvals)

                temp *= self.solid_angle  #multiply by pixel solid angle
                temp *= band.expected(
                    ps.model)  # scale by total expected counts
                point_counts += temp

        return point_counts

    def extended_source_counts(self, extended_model):

        rd = self.roi.roi_dir

        es = extended_model.extended_source
        sm = es.smodel

        extended_counts = np.zeros(len(self.wsdl), dtype=float)

        for band in self.selected_bands:
            extended_model.set_state(band)
            exposure = band.exp.value

            er = exposure(es.spatial_model.center,
                          extended_model.current_energy) / exposure(
                              rd, extended_model.current_energy)
            es_counts = band.expected(sm) * er

            wsdl = self.bigger_wsdl(band)

            es_pix_counts = extended_model._pix_value(wsdl) * self.solid_angle

            es_pix_counts = self.downsample_model(es_pix_counts)

            bg_pix_counts = es_pix_counts * es_counts

            extended_counts += bg_pix_counts

        return extended_counts

    def otf_source_counts(self, bg):
        roi = self.roi

        mo = bg.smodel

        background_counts = np.zeros(len(self.wsdl), dtype=float)

        for band in self.selected_bands:

            ns, bg_points, bg_vector = ROIDiffuseModel_OTF.sub_energy_binning(
                band, bg.nsimps)

            pi_evals = np.empty([len(self.wsdl), ns + 1])

            wsdl = self.bigger_wsdl(band, compare=bg.pixelsize / 4.0)

            for ne, e in enumerate(bg_points):
                bg.set_state(e, band.ct, band)
                temp = self.downsample_model(bg._pix_value(wsdl))
                pi_evals[:, ne] = temp

            pi_evals *= (self.solid_angle * bg_vector)
            mo_evals = mo(bg_points)
            pi_counts = (pi_evals * mo_evals).sum(axis=1)

            background_counts += pi_counts

        return background_counts

    def diffuse_source_counts(self, bg):
        if isinstance(bg, ROIDiffuseModel_OTF):
            return self.otf_source_counts(bg)
        elif isinstance(bg, ROIExtendedModel):
            return self.extended_source_counts(bg)
        else:
            raise Exception(
                "Unable to calculate model predictions for diffuse source %s",
                bg.name)

    @staticmethod
    def get_diffuse_sources(roi, override_point_sources,
                            override_diffuse_sources):

        if override_point_sources is None and override_diffuse_sources is None:
            return roi.dsm.bgmodels
        else:
            mapper = get_default_diffuse_mapper(roi.sa, roi.roi_dir, roi.quiet)
            if override_diffuse_sources is None:
                return []
            elif not isinstance(override_diffuse_sources,
                                collections.Iterable):
                return [mapper(override_diffuse_sources)]
            else:
                return [mapper(ds) for ds in override_diffuse_sources]

    def all_diffuse_sources_counts(self):
        """ Calculate the diffuse source contributions. """

        bgmodels = ModelImage.get_diffuse_sources(
            self.roi, self.override_point_sources,
            self.override_diffuse_sources)

        return sum(self.diffuse_source_counts(bg) for bg in bgmodels)