def test__convolution_inputs(): m1 = Const2D() m2 = Const2D() model = convolve_models_fft(m1, m2, ((-1, 1), (-1, 1)), 0.01) x = np.arange(-1, 1, 0.1) y = np.arange(-2, 2, 0.1) grid0 = np.meshgrid(x, x) grid1 = np.meshgrid(y, y) # scalar inputs assert (np.array([1]), (1, )) == model._convolution_inputs(1) # Multiple inputs assert np.all( model._convolution_inputs( *grid0)[0] == np.reshape([grid0[0], grid0[1]], (2, -1)).T) assert model._convolution_inputs(*grid0)[1] == grid0[0].shape assert np.all( model._convolution_inputs( *grid1)[0] == np.reshape([grid1[0], grid1[1]], (2, -1)).T) assert model._convolution_inputs(*grid1)[1] == grid1[0].shape # Error with pytest.raises(ValueError) as err: model._convolution_inputs(grid0[0], grid1[1]) assert str(err.value) ==\ "Values have differing shapes"
def evaluate(x, y, constant, amplitude, x_mean, y_mean, x_stddev, y_stddev, theta): """Two dimensional Gaussian plus constant function.""" model = Const2D(constant)(x, y) + Gaussian2D( amplitude, x_mean, y_mean, x_stddev, y_stddev, theta)(x, y) return model
def _fit_2dgaussian(data): """ Fit a 2d Gaussian to data. Written as a replace for functionality that was removed from photutils. Keep this private so we don't have to support it.... Copy/pasted from https://github.com/astropy/photutils/pull/1064/files#diff-9e64908ff7ac552845b4831870a749f397d73c681d218267bd9087c7757e6dd4R285 """ props = data_properties(data - np.min(data)) init_const = 0. # subtracted data minimum above init_amplitude = np.ptp(data) g_init = (Const2D(init_const) + Gaussian2D(amplitude=init_amplitude, x_mean=props.xcentroid, y_mean=props.ycentroid, x_stddev=props.semimajor_sigma.value, y_stddev=props.semiminor_sigma.value, theta=props.orientation.value)) fitter = LevMarLSQFitter() y, x = np.indices(data.shape) gfit = fitter(g_init, x, y, data) return gfit
def make_one_donut(center, diameter=10, amplitude=0.25): sigma = diameter / 2 mh = RickerWavelet2D(amplitude=amplitude, x_0=center[0], y_0=center[1], sigma=sigma) gauss = Gaussian2D(amplitude=amplitude, x_mean=center[0], y_mean=center[1], x_stddev=sigma, y_stddev=sigma) return Const2D(amplitude=1) + (mh - gauss)
def test_input_shape_2d(): m1 = Const2D() m2 = Const2D() model = convolve_models_fft(m1, m2, ((-1, 1), (-1, 1)), 0.01) results = model(0, 0) assert results.shape == (1, ) x = np.arange(-1, 1, 0.1) results = model(x, 0) assert results.shape == x.shape results = model(0, x) assert results.shape == x.shape grid = np.meshgrid(x, x) results = model(*grid) assert results.shape == grid[0].shape assert results.shape == grid[1].shape
def twoD_gaussian(shape=(201, 201), x_std=10., y_std=10., theta=0, amp=1., bkg=0.): centre = tuple([val // 2 for val in shape]) mod = Gaussian2D(x_mean=centre[0], y_mean=centre[1], x_stddev=x_std, y_stddev=y_std, amplitude=amp, theta=theta) + \ Const2D(amplitude=bkg) return mod
def FWHM(data, xc, yc): from astropy.modeling.models import Gaussian2D from astropy.modeling.models import Const2D from astropy.modeling.fitting import LevMarLSQFitter # Data: x, y = np.mgrid[:data.shape[1], :data.shape[0]] # Model: cte0 = np.nanmean(data) Cte = Const2D(cte0) Gauss = Gaussian2D(amplitude=data[int(yc), int(xc)], x_mean=xc, y_mean=yc) # Parametros fijos Gauss.x_mean.fixed = True Gauss.y_mean.fixed = True model = Gauss + Cte # Parametros ligados: imponemos que sea una gaussiana redonda def tie(model): return model.y_stddev_0 model.x_stddev_0.tied = tie # Fit fitter = LevMarLSQFitter() import warnings with warnings.catch_warnings(): # Ignore model linearity warning from the fitter warnings.simplefilter('ignore') fit = fitter(model, x, y, data) sigma = fit.x_stddev_0.value FWHM = 2.3548 * sigma return FWHM
def make_one_donut(center, diameter=10, amplitude=0.25): ''' This fuction is pretty eavy, it uses 3 mathematical fuction creates the image of a single donut to by added to the flat image Parameters ---------- center : numpy array center[0] : the position along the x axis of the center of the donut center[1] : the position along the y axis of the center of the donut diameter : int, float Can be interpreted as the diameter in pixel of the donut. In reality it is 2sigma of the Gaussian and RickerWavelet (ex MexicaHat) functions used to simulate the donut amplitude : float, optional The peak intensity of the aforementioned functions Output ------ Const2D(amplitude=1) + (mh - gauss) : numpy array It is a image of the donut created ''' log.info(hist()) sigma = diameter / 2 mh = RickerWavelet2D(amplitude=amplitude, x_0=center[0], y_0=center[1], sigma=sigma) gauss = Gaussian2D(amplitude=amplitude, x_mean=center[0], y_mean=center[1], x_stddev=sigma, y_stddev=sigma) return Const2D(amplitude=1) + (mh - gauss)
def centroid_2dg(data, error=None, mask=None): """ Calculate the centroid of a 2D array by fitting a 2D Gaussian (plus a constant) to the array. Non-finite values (e.g., NaN or inf) in the ``data`` or ``error`` arrays are automatically masked. These masks are combined. Parameters ---------- data : array_like The 2D data array. error : array_like, optional The 2D array of the 1-sigma errors of the input ``data``. mask : array_like (bool), optional A boolean mask, with the same shape as ``data``, where a `True` value indicates the corresponding element of ``data`` is masked. Returns ------- centroid : `~numpy.ndarray` The ``x, y`` coordinates of the centroid. """ from ..morphology import data_properties # prevent circular imports data = np.ma.asanyarray(data) if mask is not None and mask is not np.ma.nomask: mask = np.asanyarray(mask) if data.shape != mask.shape: raise ValueError('data and mask must have the same shape.') data.mask |= mask if np.any(~np.isfinite(data)): data = np.ma.masked_invalid(data) warnings.warn('Input data contains non-finite values (e.g., NaN or ' 'infs) that were automatically masked.', AstropyUserWarning) if error is not None: error = np.ma.masked_invalid(error) if data.shape != error.shape: raise ValueError('data and error must have the same shape.') data.mask |= error.mask weights = 1.0 / error.clip(min=1.e-30) else: weights = np.ones(data.shape) if np.ma.count(data) < 7: raise ValueError('Input data must have a least 7 unmasked values to ' 'fit a 2D Gaussian plus a constant.') # assign zero weight to masked pixels if data.mask is not np.ma.nomask: weights[data.mask] = 0. mask = data.mask data.fill_value = 0. data = data.filled() # Subtract the minimum of the data as a rough background estimate. # This will also make the data values positive, preventing issues with # the moment estimation in data_properties. Moments from negative data # values can yield undefined Gaussian parameters, e.g., x/y_stddev. props = data_properties(data - np.min(data), mask=mask) constant_init = 0. # subtracted data minimum above g_init = (Const2D(constant_init) + Gaussian2D(amplitude=np.ptp(data), x_mean=props.xcentroid.value, y_mean=props.ycentroid.value, x_stddev=props.semimajor_axis_sigma.value, y_stddev=props.semiminor_axis_sigma.value, theta=props.orientation.value)) fitter = LevMarLSQFitter() y, x = np.indices(data.shape) gfit = fitter(g_init, x, y, data, weights=weights) return np.array([gfit.x_mean_1.value, gfit.y_mean_1.value])
def prepare_psf_model(psfmodel, xname=None, yname=None, fluxname=None, renormalize_psf=True): """ Convert a 2D PSF model to one suitable for use with `BasicPSFPhotometry` or its subclasses. .. note:: This function is needed only in special cases where the PSF model does not have ``x_0``, ``y_0``, and ``flux`` model parameters. In particular, it is not needed for any of the PSF models provided by photutils (e.g., `~photutils.psf.EPSFModel`, `~photutils.psf.IntegratedGaussianPRF`, `~photutils.psf.FittableImageModel`, `~photutils.psf.GriddedPSFModel`, etc). Parameters ---------- psfmodel : `~astropy.modeling.Fittable2DModel` The model to assume as representative of the PSF. xname : `str` or `None`, optional The name of the ``psfmodel`` parameter that corresponds to the x-axis center of the PSF. If `None`, the model will be assumed to be centered at x=0, and a new parameter will be added for the offset. yname : `str` or `None`, optional The name of the ``psfmodel`` parameter that corresponds to the y-axis center of the PSF. If `None`, the model will be assumed to be centered at y=0, and a new parameter will be added for the offset. fluxname : `str` or `None`, optional The name of the ``psfmodel`` parameter that corresponds to the total flux of the star. If `None`, a scaling factor will be added to the model. renormalize_psf : bool, optional If `True`, the model will be integrated from -inf to inf and rescaled so that the total integrates to 1. Note that this renormalization only occurs *once*, so if the total flux of ``psfmodel`` depends on position, this will *not* be correct. Returns ------- result : `~astropy.modeling.Fittable2DModel` A new model ready to be passed into `BasicPSFPhotometry` or its subclasses. """ if xname is None: xinmod = _InverseShift(0, name='x_offset') xname = 'offset_0' else: xinmod = Identity(1) xname = xname + '_2' xinmod.fittable = True if yname is None: yinmod = _InverseShift(0, name='y_offset') yname = 'offset_1' else: yinmod = Identity(1) yname = yname + '_2' yinmod.fittable = True outmod = (xinmod & yinmod) | psfmodel.copy() if fluxname is None: outmod = outmod * Const2D(1, name='flux_scaling') fluxname = 'amplitude_3' else: fluxname = fluxname + '_2' if renormalize_psf: # we do the import here because other machinery works w/o scipy from scipy import integrate integrand = integrate.dblquad(psfmodel, -np.inf, np.inf, lambda x: -np.inf, lambda x: np.inf)[0] normmod = Const2D(1. / integrand, name='renormalize_scaling') outmod = outmod * normmod # final setup of the output model - fix all the non-offset/scale # parameters for pnm in outmod.param_names: outmod.fixed[pnm] = pnm not in (xname, yname, fluxname) # and set the names so that BasicPSFPhotometry knows what to do outmod.xname = xname outmod.yname = yname outmod.fluxname = fluxname # now some convenience aliases if reasonable outmod.psfmodel = outmod[2] if 'x_0' not in outmod.param_names and 'y_0' not in outmod.param_names: outmod.x_0 = getattr(outmod, xname) outmod.y_0 = getattr(outmod, yname) if 'flux' not in outmod.param_names: outmod.flux = getattr(outmod, fluxname) return outmod
def make_images(psf_sigma): # Define width of the source and the PSF source_sigma = 4 sigma = np.sqrt(psf_sigma**2 + source_sigma**2) amplitude = 1E3 / (2 * np.pi * sigma**2) source = Gaussian2D(amplitude, 99, 99, sigma, sigma) background = Const2D(1) model = source + background # Define data shape shape = (200, 200) y, x = np.indices(shape) # Create a new WCS object wcs = WCS(naxis=2) # Set up an Galactic projection wcs.wcs.crpix = [100.5, 100.5] wcs.wcs.cdelt = np.array([0.02, 0.02]) wcs.wcs.crval = [0, 0] wcs.wcs.ctype = ['GLON-CAR', 'GLAT-CAR'] # Fake data random_state = get_random_state(0) data = random_state.poisson(model(x, y)) # Create exclusion mask center = SkyCoord(0, 0, frame='galactic', unit='deg') circle = CircleSkyRegion(center, 0.5 * u.deg) exclusion = SkyImage(data=x, wcs=wcs).region_mask(circle) # Save data header = wcs.to_header() mask = ~exclusion.data hdu = fits.PrimaryHDU(data=mask.astype('int32'), header=header) filename = 'exclusion.fits.gz' print('Writing {}'.format(filename)) hdu.writeto(filename, clobber=True) hdu = fits.PrimaryHDU(data=data.astype('int32'), header=header) filename = 'counts.fits.gz' print('Writing {}'.format(filename)) hdu.writeto(filename, clobber=True) hdu = fits.PrimaryHDU(data=model(x, y).astype('float32'), header=header) filename = 'model.fits.gz' print('Writing {}'.format(filename)) hdu.writeto(filename, clobber=True) hdu = fits.PrimaryHDU(data=background(x, y).astype('float32'), header=header) filename = 'background.fits.gz' print('Writing {}'.format(filename)) hdu.writeto(filename, clobber=True) hdu = fits.PrimaryHDU(data=source(x, y).astype('float32'), header=header) filename = 'source.fits.gz' print('Writing {}'.format(filename)) hdu.writeto(filename, clobber=True) exposure = 1E12 * np.ones(shape) hdu = fits.PrimaryHDU(data=exposure.astype('float32'), header=header) filename = 'exposure.fits.gz' print('Writing {}'.format(filename)) hdu.writeto(filename, clobber=True)
from __future__ import print_function, division import numpy as np from astropy.modeling.models import Gaussian2D, Const2D from astropy.io import fits from astropy.wcs import WCS from gammapy.utils.random import get_random_state # Define width of the source and the PSF sigma_psf, sigma_source = 3, 4 sigma = np.sqrt(sigma_psf**2 + sigma_source**2) amplitude = 1E3 / (2 * np.pi * sigma**2) source = Gaussian2D(amplitude, 99, 99, sigma, sigma) background = Const2D(1) model = source + background # Define data shape shape = (200, 200) y, x = np.indices(shape) # Create a new WCS object w = WCS(naxis=2) # Set up an Galactic projection w.wcs.crpix = [99, 99] w.wcs.cdelt = np.array([0.02, 0.02]) w.wcs.crval = [0, 0] w.wcs.ctype = ['GLON-CAR', 'GLAT-CAR']