def test_gaussian_sum_moments(): """Check analytical against numerical solution. """ # We define three components with different flux, position and size F_1, F_2, F_3 = 100, 200, 300 sigma_1, sigma_2, sigma_3 = 15, 10, 5 x_1, x_2, x_3 = 100, 120, 70 y_1, y_2, y_3 = 100, 90, 120 # Convert into non-normalized amplitude for astropy model def A(F, sigma): return F * 1 / (2 * np.pi * sigma ** 2) # Define and evaluate models f_1 = Gaussian2D(A(F_1, sigma_1), x_1, y_1, sigma_1, sigma_1) f_2 = Gaussian2D(A(F_2, sigma_2), x_2, y_2, sigma_2, sigma_2) f_3 = Gaussian2D(A(F_3, sigma_3), x_3, y_3, sigma_3, sigma_3) F_1_image = discretize_model(f_1, (0, 200), (0, 200)) F_2_image = discretize_model(f_2, (0, 200), (0, 200)) F_3_image = discretize_model(f_3, (0, 200), (0, 200)) moments_num = measure_image_moments(F_1_image + F_2_image + F_3_image) # Compute analytical values cov_matrix = np.zeros((12, 12)) F = [F_1, F_2, F_3] sigma = [sigma_1, sigma_2, sigma_3] x = [x_1, x_2, x_3] y = [y_1, y_2, y_3] moments_ana, uncertainties = gaussian_sum_moments(F, sigma, x, y, cov_matrix) assert_allclose(moments_ana, moments_num, 1e-6) assert_allclose(uncertainties, 0)
def _make_gaussian_sources_image(self, table_bgstars, nsigma=6): "the one from photutils was too slow" image = np.zeros(self.image_shape_pix) for i in range(self.nstars_in_image): amplitude = table_bgstars['flux'][i] / ( 2. * np.pi * table_bgstars['x_stddev'][i] * table_bgstars['y_stddev'][i]) x_mean = table_bgstars['x_mean'][i] y_mean = table_bgstars['y_mean'][i] x_stddev = table_bgstars['x_stddev'][i] y_stddev = table_bgstars['y_stddev'][i] gmodel = Gaussian2D(amplitude=amplitude, x_mean=0, y_mean=0, x_stddev=x_stddev, y_stddev=y_stddev, theta=table_bgstars['theta'][i]) x_range = self._get_range(mean_val=x_mean, sigma=x_stddev, nsigma=nsigma, axis='x') y_range = self._get_range(mean_val=y_mean, sigma=y_stddev, nsigma=nsigma, axis='y') dmodel = discretize_model(model=gmodel, x_range=tuple(x_range - x_mean), y_range=tuple(y_range - y_mean), mode='oversample', factor=self.psf_oversample) image[int(x_range[0]):int(x_range[1]), int(y_range[0]):int(y_range[1])] += dmodel.T return image
def exp_kern(alpha, size, norm=True, mode='center', factor=10): """ Generate 2D, radially symmetric exponential kernel The kernels are discretized using astropy.convolution.discretize_model. Parameters ---------- alpha : float The scale length of the exponential. size : odd int Number of pixel in x & y directions. norm_array : bool, optional If True, normalize the kern array. mode : str, optional One of the following discretization modes: 'center', 'oversample', 'linear_interp', or 'integrate'. See astropy docs for details. factor : float or int Factor of oversampling. Returns ------- kern : 2D ndarray The convolution kernel. """ assert size % 2 != 0, 'ERROR: size must be odd' x_range = (-(int(size) - 1) // 2, (int(size) - 1) // 2 + 1) model = lambda x, y: np.exp(-np.sqrt(x**2 + y**2) / alpha) kern = discretize_model(model, x_range, x_range, mode=mode, factor=factor) if norm: kern /= kern.sum() return kern
def _discretized_psfmodel(self, dx, dy): minx = np.min(dx) miny = np.min(dy) maxx = np.max(dx) + 0.5 maxy = np.max(dy) + 0.5 out = discretize_model(self.psfmodel, (minx, maxx), (miny, maxy), self.discretizemode, **self._discretize_modelkwargs) return out
def test_gaussian_sum_moments(): """Check analytical against numerical solution. """ binsz = 0.02 # We define three components with different flux, position and size in pixel coordinates F_1, F_2, F_3 = 100, 200, 300 sigma_1, sigma_2, sigma_3 = 15, 10, 5 x_1, x_2, x_3 = 100, 120, 70 y_1, y_2, y_3 = 100, 90, 120 # Convert into non-normalized amplitude for astropy model def A(F, sigma): return F * 1 / (2 * np.pi * sigma**2) # Define and evaluate models f_1 = Gaussian2D(A(F_1, sigma_1), x_1, y_1, sigma_1, sigma_1) f_2 = Gaussian2D(A(F_2, sigma_2), x_2, y_2, sigma_2, sigma_2) f_3 = Gaussian2D(A(F_3, sigma_3), x_3, y_3, sigma_3, sigma_3) F_1_image = discretize_model(f_1, (0, 201), (0, 201)) F_2_image = discretize_model(f_2, (0, 201), (0, 201)) F_3_image = discretize_model(f_3, (0, 201), (0, 201)) image = WcsNDMap.create(npix=(201, 201), binsz=binsz) image.data = F_1_image + F_2_image + F_3_image moments_num = measure_image_moments(image) # Right now the flux doesn't have a unit moments_num = [moments_num[0]] + [_.value for _ in moments_num[1:]] # Compute analytical values cov_matrix = np.zeros((12, 12)) F = [F_1, F_2, F_3] sigma = np.array([sigma_1, sigma_2, sigma_3]) * binsz x, y = image.geom.wcs.wcs_pix2world([x_1, x_2, x_3], [y_1, y_2, y_3], 0) x = np.where(x > 180, x - 360, x) moments_ana, uncertainties = gaussian_sum_moments(F, sigma, x, y, cov_matrix, shift=0) assert_allclose(moments_ana, moments_num, 1e-6) assert_allclose(uncertainties, 0)
def test_gaussian_sum_moments(): """Check analytical against numerical solution. """ # We define three components with different flux, position and size in pixel coordinates F_1, F_2, F_3 = 100, 200, 300 sigma_1, sigma_2, sigma_3 = 15, 10, 5 x_1, x_2, x_3 = 100, 120, 70 y_1, y_2, y_3 = 100, 90, 120 # Convert into non-normalized amplitude for astropy model def A(F, sigma): return F * 1 / (2 * np.pi * sigma**2) # Define and evaluate models f_1 = Gaussian2D(A(F_1, sigma_1), x_1, y_1, sigma_1, sigma_1) f_2 = Gaussian2D(A(F_2, sigma_2), x_2, y_2, sigma_2, sigma_2) f_3 = Gaussian2D(A(F_3, sigma_3), x_3, y_3, sigma_3, sigma_3) F_1_image = discretize_model(f_1, (0, 201), (0, 201)) F_2_image = discretize_model(f_2, (0, 201), (0, 201)) F_3_image = discretize_model(f_3, (0, 201), (0, 201)) image = set_header(fits.ImageHDU(F_1_image + F_2_image + F_3_image)) moments_num = measure_image_moments(image) wcs = WCS(image.header) # Compute analytical values cov_matrix = np.zeros((12, 12)) F = [F_1, F_2, F_3] sigma = np.array([sigma_1, sigma_2, sigma_3]) * BINSZ origin = 0 # convention for gammapy x, y = wcs.wcs_pix2world([x_1, x_2, x_3], [y_1, y_2, y_3], origin) # Fix longitude range, is there a wcs option for this? x = np.where(x > 180, x - 360, x) moments_ana, uncertainties = gaussian_sum_moments(F, sigma, x, y, cov_matrix, shift=0) assert_allclose(moments_ana, moments_num, 1e-6) assert_allclose(uncertainties, 0)
def gauss_kern(width, size, width_type='fwhm', norm_array=False, mode='center', factor=10): """ Generate a 2D radially symmetric Gaussian kernel for sextractor, The kernels are discretized using `astropy.convolution.discretize_model`. Parameters ---------- width : float The width of the Gaussian. size : odd int Number of pixel in x & y directions. width_type : string, optional The width type given ('fwhm' or 'sigma'). norm_array : bool, optional If True, normalize the kern array. mode : str, optional One of the following discretization modes: 'center', 'oversample', 'linear_interp', or 'integrate'. See astropy docs for details. factor : float or int Factor of oversampling. Returns ------- kern : 2D ndarray, if (return_fn=False) The convolution kernel. kern, fn, comment : ndarray, string, string (if return_fn=True) The kernel, file name, and the comment for the file. Notes ----- i) The kernel will be normalized by sextractor by default, and the non-normalized files are a bit cleaner due to less leading zeros. """ assert size%2!=0, 'ERROR: size must be odd' from astropy.convolution import discretize_model convert = 2.0*np.sqrt(2*np.log(2)) if width_type=='fwhm': sigma = width/convert fwhm = width elif width_type=='sigma': sigma = width fwhm = width*convert x_range = (-(int(size) - 1) // 2, (int(size) - 1) // 2 + 1) model = lambda x, y: np.exp(-(x**2 + y**2)/(2*sigma**2)) kern = discretize_model(model, x_range, x_range, mode=mode, factor=factor) if norm_array: kern /= kern.sum() return kern
def test_gaussian_sum_moments(): """Check analytical against numerical solution. """ binsz = 0.02 # We define three components with different flux, position and size in pixel coordinates F_1, F_2, F_3 = 100, 200, 300 sigma_1, sigma_2, sigma_3 = 15, 10, 5 x_1, x_2, x_3 = 100, 120, 70 y_1, y_2, y_3 = 100, 90, 120 # Convert into non-normalized amplitude for astropy model def A(F, sigma): return F * 1 / (2 * np.pi * sigma ** 2) # Define and evaluate models f_1 = Gaussian2D(A(F_1, sigma_1), x_1, y_1, sigma_1, sigma_1) f_2 = Gaussian2D(A(F_2, sigma_2), x_2, y_2, sigma_2, sigma_2) f_3 = Gaussian2D(A(F_3, sigma_3), x_3, y_3, sigma_3, sigma_3) F_1_image = discretize_model(f_1, (0, 201), (0, 201)) F_2_image = discretize_model(f_2, (0, 201), (0, 201)) F_3_image = discretize_model(f_3, (0, 201), (0, 201)) image = WcsNDMap.create(npix=(201, 201), binsz=binsz) image.data = F_1_image + F_2_image + F_3_image moments_num = measure_image_moments(image) # Right now the flux doesn't have a unit moments_num = [moments_num[0]] + [_.value for _ in moments_num[1:]] # Compute analytical values cov_matrix = np.zeros((12, 12)) F = [F_1, F_2, F_3] sigma = np.array([sigma_1, sigma_2, sigma_3]) * binsz x, y = image.geom.wcs.wcs_pix2world([x_1, x_2, x_3], [y_1, y_2, y_3], 0) x = np.where(x > 180, x - 360, x) moments_ana, uncertainties = gaussian_sum_moments( F, sigma, x, y, cov_matrix, shift=0 ) assert_allclose(moments_ana, moments_num, 1e-6) assert_allclose(uncertainties, 0)
def test_gaussian_sum_moments(): """Check analytical against numerical solution. """ # We define three components with different flux, position and size in pixel coordinates F_1, F_2, F_3 = 100, 200, 300 sigma_1, sigma_2, sigma_3 = 15, 10, 5 x_1, x_2, x_3 = 100, 120, 70 y_1, y_2, y_3 = 100, 90, 120 # Convert into non-normalized amplitude for astropy model def A(F, sigma): return F * 1 / (2 * np.pi * sigma ** 2) # Define and evaluate models f_1 = Gaussian2D(A(F_1, sigma_1), x_1, y_1, sigma_1, sigma_1) f_2 = Gaussian2D(A(F_2, sigma_2), x_2, y_2, sigma_2, sigma_2) f_3 = Gaussian2D(A(F_3, sigma_3), x_3, y_3, sigma_3, sigma_3) F_1_image = discretize_model(f_1, (0, 201), (0, 201)) F_2_image = discretize_model(f_2, (0, 201), (0, 201)) F_3_image = discretize_model(f_3, (0, 201), (0, 201)) image = set_header(fits.ImageHDU(F_1_image + F_2_image + F_3_image)) moments_num = measure_image_moments(image) wcs = WCS(image.header) # Compute analytical values cov_matrix = np.zeros((12, 12)) F = [F_1, F_2, F_3] sigma = np.array([sigma_1, sigma_2, sigma_3]) * BINSZ origin = 0 # convention for gammapy x, y = wcs.wcs_pix2world([x_1, x_2, x_3], [y_1, y_2, y_3], origin) # Fix longitude range, is there a wcs option for this? x = np.where(x > 180, x - 360, x) moments_ana, uncertainties = gaussian_sum_moments(F, sigma, x, y, cov_matrix, shift=0) assert_allclose(moments_ana, moments_num, 1e-6) assert_allclose(uncertainties, 0)
def exp_kern(alpha, size, norm_array=False, mode='center', factor=10): """ Generate 2D, radially symmetric exponential kernal for sextractor. The kernels are discretized using `astropy.convolution.discretize_model`. Parameters ---------- alpha : float The scale length of the exponential. size : odd int Number of pixel in x & y directions. norm_array : bool, optional If True, normalize the kern array. mode : str, optional One of the following discretization modes: 'center', 'oversample', 'linear_interp', or 'integrate'. See astropy docs for details. factor : float or int Factor of oversampling. Returns ------- kern : 2D ndarray, if (return_fn=False) The convolution kernel. kern, fn, comment : ndarray, string, string (if return_fn=True) The kernel, file name, and the comment for the file. Notes ----- The kernel will be normalized by sextractor by default, and the non-normalized files are a bit cleaner due to less leading zeros. """ assert size%2!=0, 'ERROR: size must be odd' from astropy.convolution import discretize_model x_range = (-(int(size) - 1) // 2, (int(size) - 1) // 2 + 1) model = lambda x, y: np.exp(-np.sqrt(x**2 + y**2)/alpha) kern = discretize_model(model, x_range, x_range, mode=mode, factor=factor) if norm_array: kern /= kern.sum() return kern
def pixellated(self, size=21, offsets=(0.0, 0.0)): """ Calculates a pixellated version of the PSF. The pixel values are calculated using 10x oversampling, i.e. by evaluating the continuous PSF model at a 10 x 10 grid of positions within each pixel and averaging the results. Parameters ---------- size : int, optional Size of the pixellated PSF to calculate, the returned image will have `size` x `size` pixels. Default value 21. offset : tuple of floats, optional y and x axis offsets of the centre of the PSF from the centre of the returned image, in pixels. Returns ------- pixellated : numpy.array Pixellated PSF image with `size` by `size` pixels. The PSF is normalised to an integral of 1 however the sum of the pixellated PSF will be somewhat less due to truncation of the PSF wings by the edge of the image. """ size = int(size) if size <= 0: raise ValueError("`size` must be > 0, got {}!".format(size)) # Update PSF centre coordinates self.x_0 = offsets[0] self.y_0 = offsets[1] xrange = (-(size - 1) / 2, (size + 1) / 2) yrange = (-(size - 1) / 2, (size + 1) / 2) return discretize_model(self, xrange, yrange, mode='oversample', factor=10)
def make_gaussian_source_image(self, image, amplitude, x_mean, y_mean, x_stddev, y_stddev, theta, nsigma): gmodel = Gaussian2D(amplitude=amplitude, x_mean=0, y_mean=0, x_stddev=x_stddev, y_stddev=y_stddev, theta=theta) x_range = self._get_range(mean_val=x_mean, sigma=x_stddev, nsigma=nsigma, axis='x') y_range = self._get_range(mean_val=y_mean, sigma=y_stddev, nsigma=nsigma, axis='y') dmodel = discretize_model(model=gmodel, x_range=tuple(x_range - x_mean), y_range=tuple(y_range - y_mean), mode='oversample', factor=self.psf_oversample) image[int(x_range[0]):int(x_range[1]), int(y_range[0]):int(y_range[1])] += dmodel.T return image
def make_model_sources_image(shape, model, source_table, oversample=1): """ Make an image containing sources generated from a user-specified model. Parameters ---------- shape : 2-tuple of int The shape of the output 2D image. model : 2D astropy.modeling.models object The model to be used for rendering the sources. source_table : `~astropy.table.Table` Table of parameters for the sources. Each row of the table corresponds to a source whose model parameters are defined by the column names, which must match the model parameter names. Column names that do not match model parameters will be ignored. Model parameters not defined in the table will be set to the ``model`` default value. oversample : float, optional The sampling factor used to discretize the models on a pixel grid. If the value is 1.0 (the default), then the models will be discretized by taking the value at the center of the pixel bin. Note that this method will not preserve the total flux of very small sources. Otherwise, the models will be discretized by taking the average over an oversampled grid. The pixels will be oversampled by the ``oversample`` factor. Returns ------- image : 2D `~numpy.ndarray` Image containing model sources. See Also -------- make_random_models_table, make_gaussian_sources_image Examples -------- .. plot:: :include-source: from collections import OrderedDict from astropy.modeling.models import Moffat2D from photutils.datasets import (make_random_models_table, make_model_sources_image) model = Moffat2D() n_sources = 10 shape = (100, 100) param_ranges = [('amplitude', [100, 200]), ('x_0', [0, shape[1]]), ('y_0', [0, shape[0]]), ('gamma', [5, 10]), ('alpha', [1, 2])] param_ranges = OrderedDict(param_ranges) sources = make_random_models_table(n_sources, param_ranges, random_state=12345) data = make_model_sources_image(shape, model, sources) plt.imshow(data) """ image = np.zeros(shape, dtype=np.float64) y, x = np.indices(shape) params_to_set = [] for param in source_table.colnames: if param in model.param_names: params_to_set.append(param) # Save the initial parameter values so we can set them back when # done with the loop. It's best not to copy a model, because some # models (e.g. PSF models) may have substantial amounts of data in # them. init_params = {param: getattr(model, param) for param in params_to_set} try: for i, source in enumerate(source_table): for param in params_to_set: setattr(model, param, source[param]) if oversample == 1: image += model(x, y) else: image += discretize_model(model, (0, shape[1]), (0, shape[0]), mode='oversample', factor=oversample) finally: for param, value in init_params.items(): setattr(model, param, value) return image
def make_gaussian_sources(image_shape, source_table, oversample=1): """ Make an image containing 2D Gaussian sources. Parameters ---------- image_shape : 2-tuple of int Shape of the output 2D image. source_table : `astropy.table.Table` Table of parameters for the Gaussian sources. Each row of the table corresponds to a Gaussian source whose parameters are defined by the column names. The column names must include ``flux`` or ``amplitude``, ``x_mean``, ``y_mean``, ``x_stddev``, ``y_stddev``, and ``theta`` (see `~astropy.modeling.functional_models.Gaussian2D` for a description of most of these parameter names). If both ``flux`` and ``amplitude`` are present, then ``amplitude`` will be ignored. oversample : float, optional The sampling factor used to discretize the `~astropy.modeling.functional_models.Gaussian2D` models on a pixel grid. If the value is 1.0 (the default), then the models will be discretized by taking the value at the center of the pixel bin. Note that this method will not preserve the total flux of very small sources. Otherwise, the models will be discretized by taking the average over an oversampled grid. The pixels will be oversampled by the ``oversample`` factor. Returns ------- image : `numpy.ndarray` Image containing 2D Gaussian sources. See Also -------- make_random_gaussians, make_noise_image, make_poisson_noise Examples -------- .. plot:: :include-source: # make a table of Gaussian sources from astropy.table import Table table = Table() table['amplitude'] = [50, 70, 150, 210] table['x_mean'] = [160, 25, 150, 90] table['y_mean'] = [70, 40, 25, 60] table['x_stddev'] = [15.2, 5.1, 3., 8.1] table['y_stddev'] = [2.6, 2.5, 3., 4.7] table['theta'] = np.array([145., 20., 0., 60.]) * np.pi / 180. # make an image of the sources without noise, with Gaussian # noise, and with Poisson noise from photutils.datasets import make_gaussian_sources from photutils.datasets import make_noise_image shape = (100, 200) image1 = make_gaussian_sources(shape, table) image2 = image1 + make_noise_image(shape, type='gaussian', mean=0., stddev=5.) image3 = image1 + make_noise_image(shape, type='poisson', mean=5.) # plot the images import matplotlib.pyplot as plt fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(8, 12)) ax1.imshow(image1, origin='lower', interpolation='nearest') ax2.imshow(image2, origin='lower', interpolation='nearest') ax3.imshow(image3, origin='lower', interpolation='nearest') """ image = np.zeros(image_shape, dtype=np.float64) y, x = np.indices(image_shape) if 'flux' in source_table.colnames: amplitude = source_table['flux'] / ( 2. * np.pi * source_table['x_stddev'] * source_table['y_stddev']) elif 'amplitude' in source_table.colnames: amplitude = source_table['amplitude'] else: raise ValueError('either "amplitude" or "flux" must be columns in ' 'the input source_table') for i, source in enumerate(source_table): model = Gaussian2D(amplitude=amplitude[i], x_mean=source['x_mean'], y_mean=source['y_mean'], x_stddev=source['x_stddev'], y_stddev=source['y_stddev'], theta=source['theta']) if oversample == 1: image += model(x, y) else: image += discretize_model(model, (0, image_shape[1]), (0, image_shape[0]), mode='oversample', factor=oversample) return image
def make_gaussian_sources(image_shape, source_table, oversample=1): """ Make an image containing 2D Gaussian sources. Parameters ---------- image_shape : 2-tuple of int Shape of the output 2D image. source_table : `astropy.table.Table` Table of parameters for the Gaussian sources. Each row of the table corresponds to a Gaussian source whose parameters are defined by the column names. The column names must include ``flux`` or ``amplitude``, ``x_mean``, ``y_mean``, ``x_stddev``, ``y_stddev``, and ``theta`` (see `~astropy.modeling.functional_models.Gaussian2D` for a description of most of these parameter names). If both ``flux`` and ``amplitude`` are present, then ``amplitude`` will be ignored. oversample : float, optional The sampling factor used to discretize the `~astropy.modeling.functional_models.Gaussian2D` models on a pixel grid. If the value is 1.0 (the default), then the models will be discretized by taking the value at the center of the pixel bin. Note that this method will not preserve the total flux of very small sources. Otherwise, the models will be discretized by taking the average over an oversampled grid. The pixels will be oversampled by the ``oversample`` factor. Returns ------- image : `numpy.ndarray` Image containing 2D Gaussian sources. See Also -------- make_random_gaussians, make_noise_image, make_poisson_noise Examples -------- .. plot:: :include-source: # make a table of Gaussian sources from astropy.table import Table table = Table() table['amplitude'] = [50, 70, 150, 210] table['x_mean'] = [160, 25, 150, 90] table['y_mean'] = [70, 40, 25, 60] table['x_stddev'] = [15.2, 5.1, 3., 8.1] table['y_stddev'] = [2.6, 2.5, 3., 4.7] table['theta'] = np.array([145., 20., 0., 60.]) * np.pi / 180. # make an image of the sources without noise, with Gaussian # noise, and with Poisson noise from photutils.datasets import make_gaussian_sources from photutils.datasets import make_noise_image shape = (100, 200) image1 = make_gaussian_sources(shape, table) image2 = image1 + make_noise_image(shape, type='gaussian', mean=0., stddev=5.) image3 = image1 + make_noise_image(shape, type='poisson', mean=5.) # plot the images import matplotlib.pyplot as plt fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(8, 12)) ax1.imshow(image1, origin='lower', interpolation='nearest') ax2.imshow(image2, origin='lower', interpolation='nearest') ax3.imshow(image3, origin='lower', interpolation='nearest') """ image = np.zeros(image_shape, dtype=np.float64) y, x = np.indices(image_shape) if 'flux' in source_table.colnames: amplitude = source_table['flux'] / (2. * np.pi * source_table['x_stddev'] * source_table['y_stddev']) elif 'amplitude' in source_table.colnames: amplitude = source_table['amplitude'] else: raise ValueError('either "amplitude" or "flux" must be columns in ' 'the input source_table') for i, source in enumerate(source_table): model = Gaussian2D(amplitude=amplitude[i], x_mean=source['x_mean'], y_mean=source['y_mean'], x_stddev=source['x_stddev'], y_stddev=source['y_stddev'], theta=source['theta']) if oversample == 1: image += model(x, y) else: image += discretize_model(model, (0, image_shape[1]), (0, image_shape[0]), mode='oversample', factor=oversample) return image
def make_gaussian_sources(image_shape, source_table, oversample=1, unit=None, hdu=False, wcs=False, wcsheader=None): """ Make an image containing 2D Gaussian sources. Parameters ---------- image_shape : 2-tuple of int Shape of the output 2D image. source_table : `~astropy.table.Table` Table of parameters for the Gaussian sources. Each row of the table corresponds to a Gaussian source whose parameters are defined by the column names. The column names must include ``flux`` or ``amplitude``, ``x_mean``, ``y_mean``, ``x_stddev``, ``y_stddev``, and ``theta`` (see `~astropy.modeling.functional_models.Gaussian2D` for a description of most of these parameter names). If both ``flux`` and ``amplitude`` are present, then ``amplitude`` will be ignored. oversample : float, optional The sampling factor used to discretize the `~astropy.modeling.functional_models.Gaussian2D` models on a pixel grid. If the value is 1.0 (the default), then the models will be discretized by taking the value at the center of the pixel bin. Note that this method will not preserve the total flux of very small sources. Otherwise, the models will be discretized by taking the average over an oversampled grid. The pixels will be oversampled by the ``oversample`` factor. unit : `~astropy.units.UnitBase` instance, str, optional An object that represents the unit desired for the output image. Must be an `~astropy.units.UnitBase` object or a string parseable by the `~astropy.units` package. hdu : bool, optional If `True` returns ``image`` as an `~astropy.io.fits.ImageHDU` object. To include WCS information in the header, use the ``wcs`` and ``wcsheader`` inputs. Otherwise the header will be minimal. Default is `False`. wcs : bool, optional If `True` and ``hdu=True``, then a simple WCS will be included in the returned `~astropy.io.fits.ImageHDU` header. Default is `False`. wcsheader : dict or `None`, optional If ``hdu`` and ``wcs`` are `True`, this dictionary is passed to `~astropy.wcs.WCS` to generate the returned `~astropy.io.fits.ImageHDU` header. Returns ------- image : `~numpy.ndarray` or `~astropy.units.Quantity` or `~astropy.io.fits.ImageHDU` Image or `~astropy.io.fits.ImageHDU` containing 2D Gaussian sources. See Also -------- make_random_gaussians, make_noise_image, make_poisson_noise Examples -------- .. plot:: :include-source: # make a table of Gaussian sources from astropy.table import Table table = Table() table['amplitude'] = [50, 70, 150, 210] table['x_mean'] = [160, 25, 150, 90] table['y_mean'] = [70, 40, 25, 60] table['x_stddev'] = [15.2, 5.1, 3., 8.1] table['y_stddev'] = [2.6, 2.5, 3., 4.7] table['theta'] = np.array([145., 20., 0., 60.]) * np.pi / 180. # make an image of the sources without noise, with Gaussian # noise, and with Poisson noise from photutils.datasets import make_gaussian_sources from photutils.datasets import make_noise_image shape = (100, 200) image1 = make_gaussian_sources(shape, table) image2 = image1 + make_noise_image(shape, type='gaussian', mean=5., stddev=5.) image3 = image1 + make_noise_image(shape, type='poisson', mean=5.) # plot the images import matplotlib.pyplot as plt fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(8, 12)) ax1.imshow(image1, origin='lower', interpolation='nearest') ax2.imshow(image2, origin='lower', interpolation='nearest') ax3.imshow(image3, origin='lower', interpolation='nearest') """ image = np.zeros(image_shape, dtype=np.float64) y, x = np.indices(image_shape) if 'flux' in source_table.colnames: amplitude = source_table['flux'] / (2. * np.pi * source_table['x_stddev'] * source_table['y_stddev']) elif 'amplitude' in source_table.colnames: amplitude = source_table['amplitude'] else: raise ValueError('either "amplitude" or "flux" must be columns in ' 'the input source_table') for i, source in enumerate(source_table): model = Gaussian2D(amplitude=amplitude[i], x_mean=source['x_mean'], y_mean=source['y_mean'], x_stddev=source['x_stddev'], y_stddev=source['y_stddev'], theta=source['theta']) if oversample == 1: image += model(x, y) else: image += discretize_model(model, (0, image_shape[1]), (0, image_shape[0]), mode='oversample', factor=oversample) if unit is not None: image = u.Quantity(image, unit=unit) if wcs and not hdu: raise ValueError("wcs header only works with hdu output, use keyword " "'hdu=True'") if hdu is True: from astropy.io import fits if wcs: from astropy.wcs import WCS if wcsheader is None: # Go with the simplest valid header header = WCS({'CTYPE1': 'RA---TAN', 'CTYPE2': 'DEC--TAN', 'CRPIX1': int(image_shape[1] / 2), 'CRPIX2': int(image_shape[0] / 2)}, ).to_header() else: header = WCS(wcsheader) else: header = None image = fits.ImageHDU(image, header=header) return image
def make_gaussian_sources(image_shape, source_table, oversample=1, unit=None, hdu=False, wcs=False, wcsheader=None): """ Make an image containing 2D Gaussian sources. Parameters ---------- image_shape : 2-tuple of int Shape of the output 2D image. source_table : `~astropy.table.Table` Table of parameters for the Gaussian sources. Each row of the table corresponds to a Gaussian source whose parameters are defined by the column names. The column names must include ``flux`` or ``amplitude``, ``x_mean``, ``y_mean``, ``x_stddev``, ``y_stddev``, and ``theta`` (see `~astropy.modeling.functional_models.Gaussian2D` for a description of most of these parameter names). If both ``flux`` and ``amplitude`` are present, then ``amplitude`` will be ignored. oversample : float, optional The sampling factor used to discretize the `~astropy.modeling.functional_models.Gaussian2D` models on a pixel grid. If the value is 1.0 (the default), then the models will be discretized by taking the value at the center of the pixel bin. Note that this method will not preserve the total flux of very small sources. Otherwise, the models will be discretized by taking the average over an oversampled grid. The pixels will be oversampled by the ``oversample`` factor. unit : `~astropy.units.UnitBase` instance, str, optional An object that represents the unit desired for the output image. Must be an `~astropy.units.UnitBase` object or a string parseable by the `~astropy.units` package. hdu : bool, optional If `True` returns ``image`` as an `~astropy.io.fits.ImageHDU` object. To include WCS information in the header, use the ``wcs`` and ``wcsheader`` inputs. Otherwise the header will be minimal. Default is `False`. wcs : bool, optional If `True` and ``hdu=True``, then a simple WCS will be included in the returned `~astropy.io.fits.ImageHDU` header. Default is `False`. wcsheader : dict or `None`, optional If ``hdu`` and ``wcs`` are `True`, this dictionary is passed to `~astropy.wcs.WCS` to generate the returned `~astropy.io.fits.ImageHDU` header. Returns ------- image : `~numpy.ndarray` or `~astropy.units.Quantity` or `~astropy.io.fits.ImageHDU` Image or `~astropy.io.fits.ImageHDU` containing 2D Gaussian sources. See Also -------- make_random_gaussians, make_noise_image, make_poisson_noise Examples -------- .. plot:: :include-source: # make a table of Gaussian sources from astropy.table import Table table = Table() table['amplitude'] = [50, 70, 150, 210] table['x_mean'] = [160, 25, 150, 90] table['y_mean'] = [70, 40, 25, 60] table['x_stddev'] = [15.2, 5.1, 3., 8.1] table['y_stddev'] = [2.6, 2.5, 3., 4.7] table['theta'] = np.array([145., 20., 0., 60.]) * np.pi / 180. # make an image of the sources without noise, with Gaussian # noise, and with Poisson noise from photutils.datasets import make_gaussian_sources from photutils.datasets import make_noise_image shape = (100, 200) image1 = make_gaussian_sources(shape, table) image2 = image1 + make_noise_image(shape, type='gaussian', mean=5., stddev=5.) image3 = image1 + make_noise_image(shape, type='poisson', mean=5.) # plot the images import matplotlib.pyplot as plt fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(8, 12)) ax1.imshow(image1, origin='lower', interpolation='nearest') ax2.imshow(image2, origin='lower', interpolation='nearest') ax3.imshow(image3, origin='lower', interpolation='nearest') """ image = np.zeros(image_shape, dtype=np.float64) y, x = np.indices(image_shape) if 'flux' in source_table.colnames: amplitude = source_table['flux'] / ( 2. * np.pi * source_table['x_stddev'] * source_table['y_stddev']) elif 'amplitude' in source_table.colnames: amplitude = source_table['amplitude'] else: raise ValueError('either "amplitude" or "flux" must be columns in ' 'the input source_table') for i, source in enumerate(source_table): model = Gaussian2D(amplitude=amplitude[i], x_mean=source['x_mean'], y_mean=source['y_mean'], x_stddev=source['x_stddev'], y_stddev=source['y_stddev'], theta=source['theta']) if oversample == 1: image += model(x, y) else: image += discretize_model(model, (0, image_shape[1]), (0, image_shape[0]), mode='oversample', factor=oversample) if unit is not None: image = u.Quantity(image, unit=unit) if wcs and not hdu: raise ValueError("wcs header only works with hdu output, use keyword " "'hdu=True'") if hdu is True: from astropy.io import fits if wcs: from astropy.wcs import WCS if wcsheader is None: # Go with the simplest valid header header = WCS( { 'CTYPE1': 'RA---TAN', 'CTYPE2': 'DEC--TAN', 'CRPIX1': int(image_shape[1] / 2), 'CRPIX2': int(image_shape[0] / 2) }, ).to_header() else: header = WCS(wcsheader) else: header = None image = fits.ImageHDU(image, header=header) return image
def make_model_sources(image_shape, model, source_table, oversample=1, unit=None, hdu=False, wcs=False, wcsheader=None): """ Make an image containing sources generated from a user-specified flux model. Parameters ---------- image_shape : 2-tuple of int or `~numpy.ndarray` If a 2-tuple, shape of the output 2D image. If an array, these sources will be *added* to that array. model : 2D astropy.modeling.models object The model to be used for rendering the sources. source_table : `~astropy.table.Table` Table of parameters for the sources. The column names must match the model parameter names. Column names that do not match model parameters will be ignored. Any model parameter that is *not* in the table will be left at whatever value it has for ``model``. oversample : float, optional The sampling factor used to discretize the models on a pixel grid. If the value is 1.0 (the default), then the models will be discretized by taking the value at the center of the pixel bin. Note that this method will not preserve the total flux of very small sources. Otherwise, the models will be discretized by taking the average over an oversampled grid. The pixels will be oversampled by the ``oversample`` factor. unit : `~astropy.units.UnitBase` instance, str, optional An object that represents the unit desired for the output image. Must be an `~astropy.units.UnitBase` object or a string parseable by the `~astropy.units` package. hdu : bool, optional If `True` returns ``image`` as an `~astropy.io.fits.ImageHDU` object. To include WCS information in the header, use the ``wcs`` and ``wcsheader`` inputs. Otherwise the header will be minimal. Default is `False`. wcs : bool, optional If `True` and ``hdu=True``, then a simple WCS will be included in the returned `~astropy.io.fits.ImageHDU` header. Default is `False`. wcsheader : dict or `None`, optional If ``hdu`` and ``wcs`` are `True`, this dictionary is passed to `~astropy.wcs.WCS` to generate the returned `~astropy.io.fits.ImageHDU` header. Returns ------- image : `~numpy.ndarray` or `~astropy.units.Quantity` or `~astropy.io.fits.ImageHDU` Image or `~astropy.io.fits.ImageHDU` containing 2D Gaussian sources. """ image = np.zeros(image_shape, dtype=np.float64) y, x = np.indices(image_shape) params_to_set = [] for colnm in source_table.colnames: if colnm in model.param_names: params_to_set.append(colnm) # use this to store the *initial* values so we can set them back when done # with the loop. Best not to copy a model, because some PSF models may have # substantial amounts of data in them init_params = {pnm: getattr(model, pnm) for pnm in params_to_set} try: for i, source in enumerate(source_table): for paramnm in params_to_set: setattr(model, paramnm, source[paramnm]) if oversample == 1: image += model(x, y) else: image += discretize_model(model, (0, image_shape[1]), (0, image_shape[0]), mode='oversample', factor=oversample) finally: for pnm, val in init_params.items(): setattr(model, paramnm, val) if unit is not None: image = u.Quantity(image, unit=unit) if wcs and not hdu: raise ValueError("wcs header only works with hdu output, use keyword " "'hdu=True'") if hdu is True: from astropy.io import fits if wcs: from astropy.wcs import WCS if wcsheader is None: # Go with the simplest valid header header = WCS({'CTYPE1': 'RA---TAN', 'CTYPE2': 'DEC--TAN', 'CRPIX1': int(image_shape[1] / 2), 'CRPIX2': int(image_shape[0] / 2)}, ).to_header() else: header = WCS(wcsheader) else: header = None image = fits.ImageHDU(image, header=header) return image