def gen_psffield_direct(positions, fluxes=None, shape=(32, 32), kernel=None, factor=None): """Generate a point spread function field given a catalog of point source. Direct method Parameters ---------- positions : array_like, shape (2, M) x, y positions in pixel coordinates fluxes : array_like, shape (M,) corresponding peak fluxes shape : int or [int, int], optional the output image shape kernel : ~astropy.convolution.Kernel2D, optional the 2D kernel to be used for the PSF factor : [int], optional a overpixelization factor used for the projection before smoothing, by default None Returns ------- array : ndarray, shape(nx, ny) The corresponding map """ if factor is None: factor = 1 if fluxes is None: fluxes = np.ones(positions.shape[1]) if isinstance(shape, (int, np.int)): shape = [shape, shape] _shape = np.array(shape) * factor # _positions = (np.asarray(positions) + 0.5) * factor - 0.5 if kernel is not None: # Upscale the kernel with factor kernel = deepcopy(kernel) for param in ["x_stddev", "y_stddev", "width", "radius", "radius_in"]: if param in kernel._model.param_names: getattr(kernel._model, param).value *= factor Kernel2D.__init__( kernel, x_size=_round_up_to_odd_integer(kernel.shape[1] * factor), y_size=_round_up_to_odd_integer(kernel.shape[0] * factor), ), xx, yy = np.meshgrid(np.arange(_shape[1]), np.arange(_shape[0])) array = np.zeros(_shape) for position, flux in zip(positions.T, fluxes): kernel._model.x_mean = position[0] kernel._model.y_mean = position[1] kernel._model.amplitude = flux array += kernel._model(xx, yy) array = array.reshape((shape[0], factor, shape[1], factor)).sum(-1).sum(1) return array
def gauss_weightratio_kernel(self, dist=1, row=0.5): ''' Creates a 2D gauss window defined by the desired sum of weighting (ratio of weigthing, roe) between (centre - dist) and (centre + dist). This means, the inter-quartile range (IQR) has the extent of 2*dist. ''' # interquartile range (IQR) of normal distribution iqr = 1.0 / norm.ppf(0.5 + row / 2) # standard deviaton of the gaussian kernel sdev = dist * iqr / 2 # recommended size of array carrying the kernel rec_size = kernels._round_up_to_odd_integer(4.0 * sdev) if self.size != rec_size: print( "Kernel size is not properly chosen, to reflect gaussian function!" ) # normalized gaussian kernel GaussKernel = kernels.Gaussian2DKernel(stddev=sdev, x_size=self.size, y_size=self.size) kernel = GaussKernel._array / np.sum(GaussKernel._array) self.array = kernel
def __init__(self, stddev_maj, stddev_min, position_angle, support_scaling=8, **kwargs): self._model = Gaussian2D(1. / (2 * np.pi * stddev_maj * stddev_min), 0, 0, x_stddev=stddev_maj, y_stddev=stddev_min, theta=position_angle) try: from astropy.modeling.utils import ellipse_extent except ImportError: raise NotImplementedError("EllipticalGaussian2DKernel requires" " astropy 1.1b1 or greater.") max_extent = \ np.max(ellipse_extent(stddev_maj, stddev_min, position_angle)) self._default_size = \ _round_up_to_odd_integer(support_scaling * 2 * max_extent) super(EllipticalGaussian2DKernel, self).__init__(**kwargs) self._truncation = np.abs(1. - 1 / self._array.sum())
def gauss_fwhm_kernel(self, fwhm=2.0): ''' Creates a 2D gauss window defined by the desired full width at half maximum (FWHM). A window is created that features the centres' half weight at a distance of FWHM/2 from the center. ''' # factor between sigma of filter and FWHM kappa = 2.0 * np.sqrt(2 * np.log(2)) # "sigma" auf gaussian kernel sdev = fwhm / kappa # recommended size of array carrying the kernel rec_size = kernels._round_up_to_odd_integer(4.0 * sdev) if self.size != rec_size: print( "Kernel size is not properly chosen, to reflect gaussian function!" ) # normalized gaussian kernel GaussKernel = kernels.Gaussian2DKernel(stddev=sdev, x_size=self.size, y_size=self.size) kernel = GaussKernel._array / np.sum(GaussKernel._array) self.array = kernel
def __init__(self, stddev, ratio, support_scaling=8, **kwargs): amp = 1. / (2 * np.pi * stddev**2 * (ratio**2 - 1)) self._model = GaussianAnnulus2D(amp, 0, 0, stddev, ratio) self._default_size = _round_up_to_odd_integer(8 * stddev) super(AnnulusKernel, self).__init__(**kwargs) self._truncation = np.abs(1. - self._array.sum())
def __init__(self, stddev_maj, stddev_min, position_angle, support_scaling=1, **kwargs): self._model = Ellipse2D(1. / (np.pi * stddev_maj * stddev_min), 0, 0, stddev_maj, stddev_min, position_angle) try: from astropy.modeling.utils import ellipse_extent except ImportError: raise NotImplementedError("EllipticalTophat2DKernel requires" " astropy 1.1b1 or greater.") max_extent = \ np.max(ellipse_extent(stddev_maj, stddev_min, position_angle)) self._default_size = \ _round_up_to_odd_integer(support_scaling * 2 * max_extent) super(EllipticalTophat2DKernel, self).__init__(**kwargs) self._truncation = 0
def gen_psffield(positions, fluxes=None, shape=(32, 32), kernel=None, factor=None): """Generate a point spread function field given a catalog of point source. Fourier method Parameters ---------- positions : array_like, shape (2, M) x, y positions in pixel coordinates fluxes : array_like, shape (M,) corresponding peak fluxes shape : int or [int, int], optional the output image shape kernel : ~astropy.convolution.Kernel2D, optional the 2D kernel to be used for the PSF factor : [int], optional a overpixelization factor used for the projection before smoothing, by default None Returns ------- array : ndarray, shape(nx, ny) The corresponding map """ if factor is None: factor = 1 if fluxes is None: fluxes = np.ones(positions.shape[1]) if isinstance(shape, (int, np.int)): shape = [shape, shape] _shape = np.array(shape) * factor _positions = (np.asarray(positions) + 0.5) * factor - 0.5 if kernel is not None: # Upscale the kernel with factor kernel = deepcopy(kernel) for param in ["x_stddev", "y_stddev", "width", "radius", "radius_in"]: if param in kernel._model.param_names: getattr(kernel._model, param).value *= factor Kernel2D.__init__( kernel, x_size=_round_up_to_odd_integer(kernel.shape[1] * factor), y_size=_round_up_to_odd_integer(kernel.shape[0] * factor), ), # Range are maximum bins edges hist2d_kwd = {"bins": _shape, "range": ((-0.5, _shape[0] - 0.5), (-0.5, _shape[1] - 0.5))} # reverse the _positions because it needs to be y x array = np.histogram2d(*_positions[::-1], weights=fluxes, **hist2d_kwd)[0] # Remove nan if present array[np.isnan(array)] = 0 if kernel is not None: kernel.normalize("peak") array = convolve_fft(array, kernel, normalize_kernel=False, boundary="wrap") / factor ** 2 # Average rebinning onto the input shape array = array.reshape((shape[0], factor, shape[1], factor)).sum(-1).sum(1) return array
def compute_ts_map_multiscale(maps, psf_parameters, scales=[0], downsample='auto', residual=False, morphology='Gaussian2D', width=None, *args, **kwargs): """ Compute multiscale TS maps using compute_ts_map. High level TS map computation using a multi gauss PSF kernel and assuming a given source morphology. To optimize the performance the input data can be sampled down when computing TS maps on larger scales. Parameters ---------- maps : `astropy.io.fits.HDUList` HDU list containing the data. The list must contain the following HDU extensions: * 'On', Counts image * 'Background', Background image * 'Diffuse', Diffuse model image * 'ExpGammaMap', Exposure image psf_parameters : dict Dict defining the multi gauss PSF parameters. See `~gammapy.irf.multi_gauss_psf` for details. scales : list ([0]) List of scales to use for TS map computation. downsample : int ('auto') Down sampling factor. Can be set to 'auto' if the down sampling factor should be chosen automatically. residual : bool (False) Compute a TS residual map. morphology : str ('Gaussian2D') Source morphology assumption. Either 'Gaussian2D' or 'Shell2D'. Returns ------- multiscale_result : list List of `TSMapResult` objects. """ BINSZ = abs(maps[0].header['CDELT1']) shape = maps[0].data.shape multiscale_result = [] for scale in scales: log.info('Computing {0}TS map for scale {1:.3f} deg and {2}' ' morphology.'.format('residual ' if residual else '', scale, morphology)) # Sample down and require that scale parameters is at least 5 pix if downsample == 'auto': factor = int(np.select([scale < 5 * BINSZ, scale < 10 * BINSZ, scale < 20 * BINSZ, scale < 40 * BINSZ], [1, 2, 4, 4], 8)) else: factor = int(downsample) if factor == 1: log.info('No down sampling used.') downsampled = False else: if morphology == 'Shell2D': factor /= 2 log.info('Using down sampling factor of {0}'.format(factor)) downsampled = True funcs = [np.nansum, np.mean, np.nansum, np.nansum, np.nansum] maps_ = {} for map_, func in zip(maps, funcs): if downsampled: maps_[map_.name.lower()] = downsample_2N(map_.data, factor, func, shape=shape_2N(shape)) else: maps_[map_.name.lower()] = map_.data # Set up PSF and source kernel kernel = multi_gauss_psf_kernel(psf_parameters, BINSZ=BINSZ, NEW_BINSZ=BINSZ * factor, mode='oversample') if scale > 0: from astropy.convolution import convolve sigma = scale / (BINSZ * factor) if morphology == 'Gaussian2D': source_kernel = Gaussian2DKernel(sigma, mode='oversample') elif morphology == 'Shell2D': model = Shell2D(1, 0, 0, sigma, sigma * width) x_size = _round_up_to_odd_integer(2 * sigma * (1 + width) + kernel.shape[0] / 2) source_kernel = Model2DKernel(model, x_size=x_size, mode='oversample') else: raise ValueError('Unknown morphology: {}'.format(morphology)) kernel = convolve(source_kernel, kernel) kernel.normalize() # Compute TS map if residual: background = (maps_['background'] + maps_['diffuse'] + maps_['onmodel']) else: background = maps_['background'] # + maps_['diffuse'] ts_results = compute_ts_map(maps_['on'], background, maps_['expgammamap'], kernel, *args, **kwargs) log.info('TS map computation took {0:.1f} s \n'.format(ts_results.runtime)) ts_results['scale'] = scale ts_results['morphology'] = morphology if downsampled: for name, order in zip(['ts', 'sqrt_ts', 'amplitude', 'niter'], [1, 1, 1, 0]): ts_results[name] = upsample_2N(ts_results[name], factor, order=order, shape=shape) multiscale_result.append(ts_results) return multiscale_result
def compute_ts_image_multiscale(images, psf_parameters, scales=[0], downsample='auto', residual=False, morphology='Gaussian2D', width=None, **kwargs): """Compute multi-scale TS images using ``compute_ts_image``. High level TS image computation using a multi-Gauss PSF kernel and assuming a given source morphology. To optimize the performance the input data can be sampled down when computing TS images on larger scales. Parameters ---------- images : `~gammapy.image.SkyImageList` Image collection containing the data. Must contain the following: * 'counts', Counts image * 'background', Background image * 'exposure', Exposure image psf_parameters : dict Dict defining the multi gauss PSF parameters. See `~gammapy.irf.multi_gauss_psf` for details. scales : list ([0]) List of scales to use for TS image computation. downsample : int ('auto') Down sampling factor. Can be set to 'auto' if the down sampling factor should be chosen automatically. residual : bool (False) Compute a TS residual image. morphology : str ('Gaussian2D') Source morphology assumption. Either 'Gaussian2D' or 'Shell2D'. **kwargs : dict Keyword arguments forwarded to `TSImageEstimator`. Returns ------- multiscale_result : list List of `~gammapy.image.SkyImageList` objects. """ BINSZ = abs(images['counts'].wcs.wcs.cdelt[0]) shape = images['counts'].data.shape multiscale_result = [] ts_estimator = TSImageEstimator(**kwargs) for scale in scales: log.info('Computing {}TS image for scale {:.3f} deg and {}' ' morphology.'.format('residual ' if residual else '', scale, morphology)) # Sample down and require that scale parameters is at least 5 pix if downsample == 'auto': factor = int(np.select([scale < 5 * BINSZ, scale < 10 * BINSZ, scale < 20 * BINSZ, scale < 40 * BINSZ], [1, 2, 4, 4], 8)) else: factor = int(downsample) if factor == 1: log.info('No down sampling used.') downsampled = False else: if morphology == 'Shell2D': factor /= 2 log.info('Using down sampling factor of {}'.format(factor)) downsampled = True funcs = [np.nansum, np.mean, np.nansum, np.nansum, np.nansum] images_downsampled = SkyImageList() for name, func in zip(images.names, funcs): if downsampled: pad_width = symmetric_crop_pad_width(shape, shape_2N(shape)) images_downsampled[name] = images[name].pad(pad_width) images_downsampled[name] = images_downsampled[name].downsample(factor, func) else: images_downsampled[name] = images[name] # Set up PSF and source kernel kernel = multi_gauss_psf_kernel(psf_parameters, BINSZ=BINSZ, NEW_BINSZ=BINSZ * factor, mode='oversample') if scale > 0: from astropy.convolution import convolve sigma = scale / (BINSZ * factor) if morphology == 'Gaussian2D': source_kernel = Gaussian2DKernel(sigma, mode='oversample') elif morphology == 'Shell2D': model = Shell2D(1, 0, 0, sigma, sigma * width) x_size = _round_up_to_odd_integer(2 * sigma * (1 + width) + kernel.shape[0] / 2) source_kernel = Model2DKernel(model, x_size=x_size, mode='oversample') else: raise ValueError('Unknown morphology: {}'.format(morphology)) kernel = convolve(source_kernel, kernel) kernel.normalize() if residual: images_downsampled['background'].data += images_downsampled['model'].data # Compute TS image ts_results = ts_estimator.run(images_downsampled, kernel) log.info('TS image computation took {0:.1f} s \n'.format(ts_results.meta['runtime'])) ts_results.meta['MORPH'] = (morphology, 'Source morphology assumption') ts_results.meta['SCALE'] = (scale, 'Source morphology size scale in deg') if downsampled: for name, order in zip(['ts', 'sqrt_ts', 'flux', 'flux_err', 'niter'], [1, 1, 1, 0]): ts_results[name] = ts_results[name].upsample(factor, order=order) ts_results[name] = ts_results[name].crop(crop_width=pad_width) multiscale_result.append(ts_results) return multiscale_result
def compute_ts_image_multiscale(images, psf_parameters, scales=[0], downsample='auto', residual=False, morphology='Gaussian2D', width=None, *args, **kwargs): """ Compute multi-scale TS images using ``compute_ts_image``. High level TS image computation using a multi-Gauss PSF kernel and assuming a given source morphology. To optimize the performance the input data can be sampled down when computing TS images on larger scales. Parameters ---------- images : `~gammapy.image.SkyImageList` Image collection containing the data. Must contain the following: * 'counts', Counts image * 'background', Background image * 'exposure', Exposure image psf_parameters : dict Dict defining the multi gauss PSF parameters. See `~gammapy.irf.multi_gauss_psf` for details. scales : list ([0]) List of scales to use for TS image computation. downsample : int ('auto') Down sampling factor. Can be set to 'auto' if the down sampling factor should be chosen automatically. residual : bool (False) Compute a TS residual image. morphology : str ('Gaussian2D') Source morphology assumption. Either 'Gaussian2D' or 'Shell2D'. Returns ------- multiscale_result : list List of `~gammapy.image.SkyImageList` objects. """ BINSZ = abs(images['counts'].wcs.wcs.cdelt[0]) shape = images['counts'].data.shape multiscale_result = [] for scale in scales: log.info( 'Computing {0}TS image for scale {1:.3f} deg and {2}' ' morphology.'.format('residual ' if residual else '', scale, morphology) ) # Sample down and require that scale parameters is at least 5 pix if downsample == 'auto': factor = int( np.select([ scale < 5 * BINSZ, scale < 10 * BINSZ, scale < 20 * BINSZ, scale < 40 * BINSZ ], [1, 2, 4, 4], 8)) else: factor = int(downsample) if factor == 1: log.info('No down sampling used.') downsampled = False else: if morphology == 'Shell2D': factor /= 2 log.info('Using down sampling factor of {0}'.format(factor)) downsampled = True funcs = [np.nansum, np.mean, np.nansum, np.nansum, np.nansum] images2 = SkyImageList() for name, func in zip(images.names, funcs): if downsampled: pad_width = symmetric_crop_pad_width(shape, shape_2N(shape)) images2[name] = images[name].pad(pad_width) images2[name] = images2[name].downsample(factor, func) else: images2[name] = images[name] # Set up PSF and source kernel kernel = multi_gauss_psf_kernel(psf_parameters, BINSZ=BINSZ, NEW_BINSZ=BINSZ * factor, mode='oversample') if scale > 0: from astropy.convolution import convolve sigma = scale / (BINSZ * factor) if morphology == 'Gaussian2D': source_kernel = Gaussian2DKernel(sigma, mode='oversample') elif morphology == 'Shell2D': model = Shell2D(1, 0, 0, sigma, sigma * width) x_size = _round_up_to_odd_integer(2 * sigma * (1 + width) + kernel.shape[0] / 2) source_kernel = Model2DKernel(model, x_size=x_size, mode='oversample') else: raise ValueError('Unknown morphology: {}'.format(morphology)) kernel = convolve(source_kernel, kernel) kernel.normalize() if residual: images2['background'].data += images2['model'].data # Compute TS image ts_results = compute_ts_image(images2['counts'], images2['background'], images2['exposure'], kernel, *args, **kwargs) log.info('TS image computation took {0:.1f} s \n'.format( ts_results.meta['runtime'])) ts_results.meta['MORPH'] = (morphology, 'Source morphology assumption') ts_results.meta['SCALE'] = (scale, 'Source morphology size scale in deg') if downsampled: for name, order in zip(['ts', 'sqrt_ts', 'amplitude', 'niter'], [1, 1, 1, 0]): ts_results[name] = ts_results[name].upsample(factor, order=order) ts_results[name] = ts_results[name].crop(crop_width=pad_width) multiscale_result.append(ts_results) return multiscale_result
def compute_ts_map_multiscale(maps, psf_parameters, scales=[0], downsample='auto', residual=False, morphology='Gaussian2D', width=None, *args, **kwargs): """ Compute multiscale TS maps using compute_ts_map. High level TS map computation using a multi gauss PSF kernel and assuming a given source morphology. To optimize the performance the input data can be sampled down when computing TS maps on larger scales. Parameters ---------- maps : `astropy.io.fits.HDUList` HDU list containing the data. The list must contain the following HDU extensions: * 'On', Counts image * 'Background', Background image * 'Diffuse', Diffuse model image * 'ExpGammaMap', Exposure image psf_parameters : dict Dict defining the multi gauss PSF parameters. See `~gammapy.irf.multi_gauss_psf` for details. scales : list ([0]) List of scales to use for TS map computation. downsample : int ('auto') Down sampling factor. Can be set to 'auto' if the down sampling factor should be chosen automatically. residual : bool (False) Compute a TS residual map. morphology : str ('Gaussian2D') Source morphology assumption. Either 'Gaussian2D' or 'Shell2D'. Returns ------- multiscale_result : list List of `TSMapResult` objects. """ BINSZ = abs(maps[0].header['CDELT1']) shape = maps[0].data.shape multiscale_result = [] for scale in scales: logging.info('Computing {0}TS map for scale {1:.3f} deg and {2}' ' morphology.'.format('residual ' if residual else '', scale, morphology)) # Sample down and require that scale parameters is at least 5 pix if downsample == 'auto': factor = int( np.select([ scale < 5 * BINSZ, scale < 10 * BINSZ, scale < 20 * BINSZ, scale < 40 * BINSZ ], [1, 2, 4, 8], 16)) else: factor = int(downsample) if factor == 1: logging.info('No down sampling used.') downsampled = False else: if morphology == 'Shell2D': factor /= 2 logging.info('Using down sampling factor of {0}'.format(factor)) downsampled = True funcs = [np.nansum, np.mean, np.nansum, np.nansum, np.nansum] maps_ = {} for map_, func in zip(maps, funcs): if downsampled: maps_[map_.name.lower()] = downsample_2N(map_.data, factor, func, shape=shape_2N(shape)) else: maps_[map_.name.lower()] = map_.data # Set up PSF and source kernel kernel = multi_gauss_psf_kernel(psf_parameters, BINSZ=BINSZ, NEW_BINSZ=BINSZ * factor, mode='oversample') if scale > 0: from astropy.convolution import convolve sigma = scale / (BINSZ * factor) if morphology == 'Gaussian2D': source_kernel = Gaussian2DKernel(sigma, mode='oversample') elif morphology == 'Shell2D': model = Shell2D(1, 0, 0, sigma, sigma * width) x_size = _round_up_to_odd_integer(2 * sigma * (1 + width) + kernel.shape[0] / 2) source_kernel = Model2DKernel(model, x_size=x_size, mode='oversample') else: raise ValueError('Unknown morphology: {}'.format(morphology)) kernel = convolve(source_kernel, kernel) kernel.normalize() # Compute TS map if residual: background = (maps_['background'] + maps_['diffuse'] + maps_['onmodel']) else: background = maps_['background'] # + maps_['diffuse'] ts_results = compute_ts_map(maps_['on'], background, maps_['expgammamap'], kernel, *args, **kwargs) logging.info('TS map computation took {0:.1f} s \n'.format( ts_results.runtime)) ts_results['scale'] = scale ts_results['morphology'] = morphology if downsampled: for name, order in zip(['ts', 'sqrt_ts', 'amplitude', 'niter'], [1, 1, 1, 0]): ts_results[name] = upsample_2N(ts_results[name], factor, order=order, shape=shape) multiscale_result.append(ts_results) return multiscale_result