Source code for photutils.psf.simulation

# Licensed under a 3-clause BSD style license - see LICENSE.rst
"""
Tools for creating images from PSF models.
"""

import numpy as np

from photutils.datasets import make_model_image, make_model_params
from photutils.datasets.images import _model_shape_from_bbox
from photutils.psf.utils import _get_psf_model_main_params
from photutils.utils._parameters import as_pair

__all__ = ['make_psf_model_image']


[docs] def make_psf_model_image(shape, psf_model, n_sources, *, model_shape=None, min_separation=1, border_size=None, seed=0, progress_bar=False, **kwargs): """ Make an example image containing PSF model images. Source parameters are randomly generated using an optional ``seed``. Parameters ---------- shape : 2-tuple of int The shape of the output image. psf_model : 2D `astropy.modeling.Model` The PSF model. The model must have parameters named ``x_0``, ``y_0``, and ``flux``, corresponding to the center (x, y) position and flux, or it must have 'x_name', 'y_name', and 'flux_name' attributes that map to the x, y, and flux parameters (i.e., a model output from `make_psf_model`). The model must be two-dimensional such that it accepts 2 inputs (e.g., x and y) and provides 1 output. n_sources : int The number of sources to generate. If ``min_separation`` is too large, the number of requested sources may not fit within the given ``shape`` and therefore the number of sources generated may be less than ``n_sources``. model_shape : `None` or 2-tuple of int, optional The shape around the center (x, y) position that will used to evaluate the ``psf_model``. If `None`, then the shape will be determined from the ``psf_model`` bounding box (an error will be raised if the model does not have a bounding box). min_separation : float, optional The minimum separation between the centers of two sources. Note that if the minimum separation is too large, the number of sources generated may be less than ``n_sources``. border_size : `None`, tuple of 2 int, or int, optional The (ny, nx) size of the exclusion border around the image edges where no sources will be generated that have centers within the border region. If a single integer is provided, it will be used for both dimensions. If `None`, then a border size equal to half the (y, x) size of the evaluated PSF model (taking any oversampling into account) will be used. seed : int, optional A seed to initialize the `numpy.random.BitGenerator`. If `None`, then fresh, unpredictable entropy will be pulled from the OS. progress_bar : bool, optional Whether to display a progress bar when creating the sources. The progress bar requires that the `tqdm <https://tqdm.github.io/>`_ optional dependency be installed. **kwargs Keyword arguments are accepted for additional model parameters. The values should be 2-tuples of the lower and upper bounds for the parameter range. The parameter values will be uniformly sampled over the half-open interval defined by the lower and upper bounds (i.e., the lower bound is inclusive and the upper bound is exclusive). A ``flux`` keyword is mapped to the model's flux parameter name (e.g., for models output from `make_psf_model`). If the parameter is not in the input ``psf_model`` parameter names, it will be ignored. Keywords matching the model's x and y position parameter names are also ignored because the source positions are randomly generated. Returns ------- data : 2D `~numpy.ndarray` The simulated image. table : `~astropy.table.Table` A table containing the (x, y, flux) parameters of the generated sources. The column names will correspond to the names of the input ``psf_model`` (x, y, flux) parameter names. The table will also contain an ``'id'`` column with unique source IDs. Examples -------- >>> from photutils.psf import CircularGaussianPRF, make_psf_model_image >>> shape = (150, 200) >>> psf_model = CircularGaussianPRF(fwhm=3.5) >>> n_sources = 10 >>> data, params = make_psf_model_image(shape, psf_model, n_sources, ... flux=(100, 250), ... min_separation=10, ... seed=0) >>> params['x_0'].info.format = '.4f' # optional format >>> params['y_0'].info.format = '.4f' >>> params['flux'].info.format = '.4f' >>> print(params) id x_0 y_0 flux --- -------- -------- -------- 1 125.2010 72.3184 147.9522 2 57.6408 39.1380 128.1262 3 15.5391 115.4520 200.8790 4 11.0411 131.7530 129.2661 5 157.6417 43.6615 186.6532 6 175.9470 80.2172 190.3359 7 142.2274 132.7563 244.3635 8 108.0270 13.4284 110.8398 9 180.0533 106.0888 174.9959 10 158.1171 90.3260 211.6146 .. plot:: :include-source: import matplotlib.pyplot as plt from photutils.psf import CircularGaussianPRF, make_psf_model_image shape = (150, 200) psf_model = CircularGaussianPRF(fwhm=3.5) n_sources = 10 data, params = make_psf_model_image(shape, psf_model, n_sources, flux=(100, 250), min_separation=10, seed=0) fig, ax = plt.subplots() ax.imshow(data, origin='lower') """ main_params = _get_psf_model_main_params(psf_model) if model_shape is not None: model_shape = as_pair('model_shape', model_shape, lower_bound=(0, 0)) else: try: model_shape = _model_shape_from_bbox(psf_model) except ValueError as exc: msg = ('model_shape must be specified if the model does not ' 'have a bounding_box attribute') raise ValueError(msg) from exc if border_size is None: border_size = (np.array(model_shape) - 1) // 2 other_params = {} if kwargs: # Include only kwargs that are model parameters, but not the # x and y position parameters for key, val in kwargs.items(): param = key if key == 'flux' and 'flux' not in psf_model.param_names: # Map the flux kwarg to the model's flux parameter name param = main_params[2] if (param not in psf_model.param_names or param in main_params[0:2]): continue # skip the x, y parameters other_params[param] = val x_name, y_name = main_params[0:2] params = make_model_params(shape, n_sources, x_name=x_name, y_name=y_name, min_separation=min_separation, border_size=border_size, seed=seed, **other_params) data = make_model_image(shape, psf_model, params, model_shape=model_shape, x_name=x_name, y_name=y_name, progress_bar=progress_bar) return data, params