class ROISignificance(ROIMapPlotter): """ Make a plot of the poisson significance for the observed counts within a circual aperature of radius . """ defaults = ROIMapPlotter.defaults + ( ('kernel_rad', 0.25, 'Sum counts/model within radius degrees.'), ('conv_type', -1, 'Conversion type'), ) defaults = keyword_options.change_defaults(defaults, 'title', 'Poisson Significance') def create_pyfits(self): # Fit many pixels inside of the summing radius pixelsize = self.kernel_rad / 10.0 kwargs = dict(size=self.size, pixelsize=pixelsize, galactic=self.galactic, conv_type=self.conv_type, kerneltype='tophat', kernel_rad=self.kernel_rad) counts = SmoothedCounts(self.roi, **kwargs) model = SmoothedModel(self.roi, **kwargs) pyfits = counts.get_pyfits() pyfits['PRIMARY'].data = ROISignificance.poisson_sigma( counts.image, model.image) return pyfits @staticmethod def poisson_sigma(obs, pred): """ Compute the sigma of the detection if you observe a given number (obs) of counts and predict a given number (pred) of counts. Note, it is numerically easier to deal with numbers close to 0 then close to 1, so compute the sigma from the cdf or the sf depending upon which is smaller. """ cdf = poisson.cdf(obs, pred) sf = poisson.sf(obs, pred) return np.where(cdf < 0.5, norm.ppf(cdf), norm.isf(sf))
class ROISmoothedBeforeAfter(object): """ Make a 2x1 plot where the left plot has the diffuse subtracted and the right plot has the diffuse and all sources subtracted. This plot is nice for seeing how well the background source subtracting is doing. """ defaults = ROISmoothedSources.defaults defaults = keyword_options.change_defaults(defaults, 'figsize', (8, 3.5)) defaults = keyword_options.change_defaults(defaults, 'title', None) @keyword_options.decorate(defaults) def __init__(self, roi, **kwargs): if kwargs.has_key('show_colorbar') or \ kwargs.has_key('overlay_psf'): raise Exception("This feature doesn't work, for now...") keyword_options.process(self, kwargs) self.roi = roi sources_kwargs = keyword_options.defaults_to_kwargs( self, ROISmoothedSources) sources_kwargs['show_colorbar'] = False self.smoothed_sources = ROISmoothedSources(roi, **sources_kwargs) source_kwargs = keyword_options.defaults_to_kwargs( self, ROISmoothedSources) source_kwargs['overlay_psf'] = False self.smoothed_source = ROISmoothedSource(roi, **source_kwargs) def show(self, filename=None): from mpl_toolkits.axes_grid1.axes_grid import ImageGrid import pywcsgrid2 self.fig = fig = P.figure(self.fignum, self.figsize) P.clf() header = self.smoothed_source.pf[0].header self.grid = grid = ImageGrid(fig, (1, 1, 1), nrows_ncols=(1, 2), axes_pad=0.1, share_all=True, cbar_mode="single", cbar_pad="2%", cbar_location="right", axes_class=(pywcsgrid2.Axes, dict(header=header))) def show(): self.smoothed_sources.show(axes=self.grid[0]) self.smoothed_source.show(axes=self.grid[1], cax=grid.cbar_axes[0]) show() # use same color scale for each plot max_intensity = max(self.smoothed_sources.max_intensity, self.smoothed_source.max_intensity) self.smoothed_sources.max_intensity = self.smoothed_source.max_intensity = max_intensity show() tight_layout(self.fig) self.header = self.h = [ self.smoothed_sources.header, self.smoothed_source.header ] if filename is not None: P.savefig(filename)
class ROISmoothedDataModel(object): """ Plot (on the left) the diffuse subtracted smoothed counts and (on the right) the diffuse subtrcted smoothed model predicted counts. Useful to see if your model (qualitativly) looks like the right source. """ defaults = ROISmoothedSources.defaults defaults = keyword_options.change_defaults(defaults, 'figsize', (8, 3.5)) defaults = keyword_options.change_defaults(defaults, 'title', None) @keyword_options.decorate(defaults) def __init__(self, roi, **kwargs): keyword_options.process(self, kwargs) self.roi = roi self.cmap = colormaps.b # Fit many pixels inside of the summing radius self.pixelsize = self.kernel_rad / 5.0 # Background subtracted counts counts_kwargs = keyword_options.defaults_to_kwargs( self, ROISmoothedSources) counts_kwargs['show_colorbar'] = False counts_kwargs['overlay_psf'] = False self.counts = ROISmoothedSources(roi, **counts_kwargs) # Model counts for non-background sources. model_kwargs = keyword_options.defaults_to_kwargs( self, ROISmoothedSources) model_kwargs['overlay_psf'] = False self.model = ROISmoothedModel(roi, **model_kwargs) def show(self, filename=None): from mpl_toolkits.axes_grid1.axes_grid import ImageGrid import pywcsgrid2 self.fig = fig = P.figure(self.fignum, self.figsize) P.clf() header = self.counts.pf[0].header self.grid = grid = ImageGrid(fig, (1, 1, 1), nrows_ncols=(1, 2), axes_pad=0.1, share_all=True, cbar_mode="single", cbar_pad="2%", cbar_location="right", axes_class=(pywcsgrid2.Axes, dict(header=header))) # This is kind of ugly, but use show() once to set the max_intensity, then show again # with matching colormaps. Kluge, but in an unimportant function. def show(): self.counts.show(axes=self.grid[0]) self.model.show(axes=self.grid[1], cax=grid.cbar_axes[0]) show() # Same colorscale kluge as in ROISmoothedBeforeAfter max_intensity = max(self.counts.max_intensity, self.model.max_intensity) self.counts.max_intensity = self.model.max_intensity = max_intensity show() self.grid[0].add_inner_title("Counts", loc=2) self.grid[1].add_inner_title("Model", loc=2) tight_layout(self.fig) self.header = self.h = [self.counts.header, self.model.header] if filename is not None: P.savefig(filename)
class ROISmoothedSources(ROIMapPlotter): """ Make a smoothed residual plot which subtracts the diffuse emission. """ defaults = ROIMapPlotter.defaults + ( ('which', None, 'Draw the smoothed point version of this source.'), ('conv_type', -1, 'Conversion type'), ('overlay_psf', True, 'Add a smoothed reference PSF on top of the counts.'), ('label_psf', False, 'Add a label on the PSF box.'), ('psf_size', 1, 'Size of the PSF insert box'), ('psf_loc', 4, 'Location to put the psf box.'), ('kerneltype', 'gaussian', 'Type of kernel to smooth image with'), ('kernel_rad', 0.1, 'Sum counts/model within radius degrees.'), ('override_center', None, 'Pick a better center'), ('cmap', None, 'Show the colorbar'), ('colorbar_radius', None, """ If specified, calculate the intensity maximum pixel to be the maximum of all pixels within this radius. This is useful if you want to clip out a very bright nearby sources. By default, this radius will be the source size or the PSF size (if which!=None). If which==None, the default is that colorbar_radius=inf. """ ), ('pixelsize_fraction', 5.0, 'The pixelsize is kernel_rad/pizelsize_fraction'), ) defaults = keyword_options.change_defaults(defaults, 'title', 'Smoothed Counts Map') def get_residual(self, **kwargs): """ Allow the particular method for getting the residual image to be overloaded. """ residual = SmoothedResidual(self.roi, override_diffuse_sources=[ i for i in self.roi.dsm.diffuse_sources if not hasattr(i, 'skydir') ], **kwargs) return residual def create_pyfits(self): # Fit many pixels inside of the summing radius self.pixelsize = self.kernel_rad / self.pixelsize_fraction roi = self.roi if self.which is not None: self.source = roi.get_source(self.which) self.center = self.source.skydir else: self.source = None self.center = roi.roi_dir if self.override_center is not None: self.center = self.override_center self.smoothed_kwargs = dict(size=self.size, pixelsize=self.pixelsize, galactic=self.galactic, conv_type=self.conv_type, center=self.center, per_solid_angle=True, kerneltype=self.kerneltype, kernel_rad=self.kernel_rad) residual = self.get_residual(**self.smoothed_kwargs) return residual.get_pyfits() def imshow_kwargs(self): self.max_intensity = ROISmoothedSources.get_max_intensity( self.source, self.pf, self.roi, colorbar_radius=self.colorbar_radius) # sanity check self.max_intensity = max(self.max_intensity, 1) if self.cmap is None: self.cmap = colormaps.b imshow_kwargs = dict(cmap=self.cmap, vmin=0, vmax=self.max_intensity) return imshow_kwargs @staticmethod def get_max_intensity(source, pyfits, roi, colorbar_radius=None): """ Return the maximum value in the pyfits file either within the extended source's size or otherwise within the 68% containment radius of the PSF (at the lowest energy). """ try: import pyregion except: return pyfits[0].data.max() if source is None and colorbar_radius is None: return pyfits[0].data.max() if colorbar_radius is not None: ra, dec = roi.roi_dir.ra(), roi.roi_dir.dec() reg = pyregion.parse("fk5; circle(%.4f, %.4f, %.4f)" % (ra, dec, colorbar_radius)) extensionmask = reg.get_mask(pyfits[0]) elif hasattr(source, 'spatial_model'): # For extended sources, # Get the maximum intensity value inside # the spatial model's extension extension_string = '\n'.join( region_writer.unparse_extension(source.spatial_model, r68=True)) reg = pyregion.parse(extension_string) extensionmask = reg.get_mask(pyfits[0]) else: extensionmask = False # no mask # Get the maximum intensity inside the PSF (in lowest bin) emin = roi.bin_edges[0] ra, dec = source.skydir.ra(), source.skydir.dec() r68 = roi.sa.psf.inverse_integral(emin, 1, 68) # 1=front entering events reg = pyregion.parse("fk5; circle(%.4f, %.4f, %.4f)" % (ra, dec, r68)) psfmask = reg.get_mask(pyfits[0]) # use whatever region mask is bigger return pyfits[0].data[psfmask | extensionmask].max() def _additional_plotting(self): from mpl_toolkits.axes_grid1.inset_locator import zoomed_inset_axes import pywcsgrid2 if self.overlay_psf: if self.source is not None: model = self.source.model.copy() else: model = PowerLaw(index=2) # convert it to a point source placed at the origin point_version = PointSource(name='PSF', skydir=self.center, model=model) # create an image of the PSF (for our model). # Shrink down the image of the psf psf_kwargs = copy.deepcopy(self.smoothed_kwargs) psf_kwargs['size'] = self.psf_size self.psf_model = SmoothedModel( self.roi, override_point_sources=[point_version], **psf_kwargs) self.psf_pyfits = self.psf_model.get_pyfits() # Normalize psf to have same maximum pixel scale # as residual image. self.psf_pyfits[0].data *= self.max_intensity / np.max( self.psf_pyfits[0].data) h_psf, d_psf = self.psf_pyfits[0].header, self.psf_pyfits[0].data self.axins = axins = zoomed_inset_axes(self.axes, zoom=1, loc=self.psf_loc, axes_class=pywcsgrid2.Axes, axes_kwargs=dict(wcs=h_psf)) # Note, match color maps with parent. axins.imshow(d_psf, origin="lower", **self.imshow_kwargs()) axins.axis[:].set_zorder(100) axins.axis[:].toggle(all=False) axins.axis[:].line.set_color('white') if self.label_psf: axins.add_inner_title("PSF", loc=3) self.cax.set_ylabel(r'$\mathrm{counts}/[\mathrm{deg}]^2$')
class ROISourceBandPlotter(ROIBandPlotter): object = ROISmoothedSource defaults = object.defaults defaults = keyword_options.change_defaults(defaults, 'figsize', (9, 4))
class ROITSMapBandPlotter(ROIBandPlotter): object = ROITSMapPlotter defaults = object.defaults defaults = keyword_options.change_defaults(defaults, 'figsize', (9, 4))
class ROISignificanceBandPlotter(ROIBandPlotter): object = ROISignificance defaults = object.defaults defaults = keyword_options.change_defaults(defaults, 'figsize', (9, 4))