Exemplo n.º 1
0
    def __init__(self,
                 image_data,
                 exposure_time=None,
                 background_rms=None,
                 noise_map=None,
                 ra_at_xy_0=0,
                 dec_at_xy_0=0,
                 transform_pix2angle=None,
                 ra_shift=0,
                 dec_shift=0):
        """

        :param image_data: 2d numpy array of the image data
        :param exposure_time: int or array of size the data; exposure time
        (common for all pixels or individually for each individual pixel)
        :param background_rms: root-mean-square value of Gaussian background noise
        :param noise_map: int or array of size the data; joint noise sqrt(variance) of each individual pixel.
        :param transform_pix2angle: 2x2 matrix, mapping of pixel to coordinate
        :param ra_at_xy_0: ra coordinate at pixel (0,0)
        :param dec_at_xy_0: dec coordinate at pixel (0,0)
        :param ra_shift: RA shift of pixel grid
        :param dec_shift: DEC shift of pixel grid
        """
        nx, ny = np.shape(image_data)
        self._data = image_data
        if transform_pix2angle is None:
            transform_pix2angle = np.array([[1, 0], [0, 1]])
        PixelGrid.__init__(self, nx, ny, transform_pix2angle,
                           ra_at_xy_0 + ra_shift, dec_at_xy_0 + dec_shift)
        ImageNoise.__init__(self,
                            image_data,
                            exposure_time=exposure_time,
                            background_rms=background_rms,
                            noise_map=noise_map,
                            verbose=False)
Exemplo n.º 2
0
    def test_raise(self):
        Mpix2coord = np.array([[1, 0], [0, 1]])
        kwargs_grid = {
            'ra_at_xy_0': 0,
            'dec_at_xy_0': 0,
            'transform_pix2angle': Mpix2coord,
            'nx': 10,
            'ny': 10
        }
        pixel_grid = PixelGrid(**kwargs_grid)
        kernel = np.zeros((5, 5))
        kernel[2, 2] = 1
        kwargs_psf = {
            'kernel_point_source': kernel,
            'psf_type': 'PIXEL',
            'psf_error_map': np.ones_like(kernel)
        }
        psf_class = PSF(**kwargs_psf)

        self._ps_rendering = PointSourceRendering(pixel_grid,
                                                  supersampling_factor=1,
                                                  psf=psf_class)
        with self.assertRaises(ValueError):
            self._ps_rendering.point_source_rendering(ra_pos=[1, 1],
                                                      dec_pos=[0, 1],
                                                      amp=[1])
    def __init__(self,
                 multi_band_list,
                 kwargs_model,
                 kwargs_params,
                 multi_band_type='joint-linear',
                 kwargs_likelihood=None,
                 kwargs_pixel_grid=None,
                 verbose=True):
        """

        :param multi_band_list: list of imaging data configuration [[kwargs_data, kwargs_psf, kwargs_numerics], [...]]
        :param kwargs_model: model keyword argument list
        :param kwargs_params: keyword arguments of the model parameters, same as output of FittingSequence() 'kwargs_result'
        :param multi_band_type: string, option when having multiple imaging data sets modelled simultaneously. Options are:
            - 'multi-linear': linear amplitudes are inferred on single data set
            - 'linear-joint': linear amplitudes ae jointly inferred
            - 'single-band': single band
        :param kwargs_likelihood: likelihood keyword arguments as supported by the Likelihood() class
        :param kwargs_pixel_grid: keyword argument of PixelGrid() class. This is optional and overwrites a minimal grid
         Attention for consistent pixel grid definitions!
        :param verbose: if True (default), computes and prints the total log-likelihood.
        This can deactivated for speedup purposes (does not run linear inversion again), and reduces the number of prints.
        """
        self._multi_band_list = multi_band_list
        if not multi_band_type == 'joint-linear':
            raise ValueError(
                'MultiPatchPlot only works with multi_band_type="joint_linear". '
                'Setting choice was %s. ' % multi_band_type)
        MultiBandImageReconstruction.__init__(
            self,
            multi_band_list,
            kwargs_model,
            kwargs_params,
            multi_band_type=multi_band_type,
            kwargs_likelihood=kwargs_likelihood,
            verbose=verbose)
        if kwargs_pixel_grid is not None:
            self._pixel_grid_joint = PixelGrid(**kwargs_pixel_grid)
        else:
            self._pixel_grid_joint = self._joint_pixel_grid(multi_band_list)
Exemplo n.º 4
0
    def _sub_pixel_grid(self, pixel_grid):
        """
        creates a PixelGrid instance covering the sub-frame area only

        :param pixel_grid: PixelGrid instance of the full image
        :return: PixelGrid instance
        """
        if self._subframe_calc is True:
            transform_pix2angle = pixel_grid.transform_pix2angle
            nx_sub = self._x_max_sub - self._x_min_sub + 1
            ny_sub = self._y_max_sub - self._y_min_sub + 1
            ra_at_xy_0_sub, dec_at_xy_0_sub = pixel_grid.map_pix2coord(self._x_min_sub, self._y_min_sub)
            pixel_grid_sub = PixelGrid(nx=nx_sub, ny=ny_sub, transform_pix2angle=transform_pix2angle,
                                       ra_at_xy_0=ra_at_xy_0_sub, dec_at_xy_0=dec_at_xy_0_sub)
        else:
            pixel_grid_sub = pixel_grid
        return pixel_grid_sub
    def source(self, num_pix, delta_pix, center=None):
        """
        source in the same coordinate system as the image

        :param num_pix: number of pixels per axes
        :param delta_pix: pixel size
        :param center: list with two entries [center_x, center_y] (optional)
        :return: 2d surface brightness grid of the reconstructed source and PixelGrid() instance of source grid
        """
        Mpix2coord = self._pixel_grid_joint.transform_pix2angle * delta_pix / self._pixel_grid_joint.pixel_width
        x_grid_source, y_grid_source = util.make_grid_transformed(
            num_pix, Mpix2Angle=Mpix2coord)
        ra_at_xy_0, dec_at_xy_0 = x_grid_source[0], y_grid_source[0]

        image_model = self.model_band_list[0].image_model_class
        kwargs_model = self.model_band_list[0].kwargs_model
        kwargs_source = kwargs_model['kwargs_source']

        center_x = 0
        center_y = 0
        if center is not None:
            center_x, center_y = center[0], center[1]
        elif len(kwargs_source) > 0:
            center_x = kwargs_source[0]['center_x']
            center_y = kwargs_source[0]['center_y']
        x_grid_source += center_x
        y_grid_source += center_y

        pixel_grid = PixelGrid(nx=num_pix,
                               ny=num_pix,
                               transform_pix2angle=Mpix2coord,
                               ra_at_xy_0=ra_at_xy_0 + center_x,
                               dec_at_xy_0=dec_at_xy_0 + center_y)

        source = image_model.SourceModel.surface_brightness(
            x_grid_source, y_grid_source, kwargs_source)
        source = util.array2image(source) * delta_pix**2
        return source, pixel_grid
    def _joint_pixel_grid(multi_band_list):
        """
        Joint PixelGrid() class instance.
        This routine only works when the individual patches have the same coordinate system orientation and pixel scale.

        :param multi_band_list: list of imaging data configuration [[kwargs_data, kwargs_psf, kwargs_numerics], [...]]
        :return: PixelGrid() class instance covering the entire window of the sky including all individual patches
        """

        nx, ny = 0, 0
        kwargs_data = copy.deepcopy(multi_band_list[0][0])
        kwargs_pixel_grid = {
            'nx': 0,
            'ny': 0,
            'transform_pix2angle': kwargs_data['transform_pix2angle'],
            'ra_at_xy_0': kwargs_data['ra_at_xy_0'],
            'dec_at_xy_0': kwargs_data['dec_at_xy_0']
        }
        pixel_grid = PixelGrid(**kwargs_pixel_grid)
        Mpix2a = pixel_grid.transform_pix2angle

        # set up joint coordinate system and pixel size to include all frames
        for i in range(len(multi_band_list)):
            kwargs_data = multi_band_list[i][0]
            data_class_i = ImageData(**kwargs_data)
            Mpix2a_i = data_class_i.transform_pix2angle
            # check we are operating in the same coordinate system/rotation and pixel scale
            npt.assert_almost_equal(Mpix2a, Mpix2a_i, decimal=5)

            # evaluate pixel of zero point with the base coordinate system
            ra0, dec0 = data_class_i.radec_at_xy_0
            x_min, y_min = pixel_grid.map_coord2pix(ra0, dec0)
            nx_i, ny_i = data_class_i.num_pixel_axes
            nx, ny = _update_frame_size(nx, ny, x_min, y_min, nx_i, ny_i)

            # select minimum in x- and y-axis
            # transform back in RA/DEC and make this the new zero point of the base coordinate system
            ra_at_xy_0_new, dec_at_xy_0_new = pixel_grid.map_pix2coord(
                np.minimum(x_min, 0), np.minimum(y_min, 0))
            kwargs_pixel_grid['ra_at_xy_0'] = ra_at_xy_0_new
            kwargs_pixel_grid['dec_at_xy_0'] = dec_at_xy_0_new
            kwargs_pixel_grid['nx'] = nx
            kwargs_pixel_grid['ny'] = ny
            pixel_grid = PixelGrid(**kwargs_pixel_grid)
        return pixel_grid
Exemplo n.º 7
0
    def setup(self):

        # we define a model consisting of a singe Sersric profile
        from lenstronomy.LightModel.light_model import LightModel
        light_model_list = ['SERSIC_ELLIPSE']
        self.lightModel = LightModel(light_model_list=light_model_list)
        self.kwargs_light = [
            {'amp': 100, 'R_sersic': 0.5, 'n_sersic': 3, 'e1': 0, 'e2': 0, 'center_x': 0.02, 'center_y': 0}]

        # we define a pixel grid and a higher resolution super sampling factor
        self._supersampling_factor = 5
        numPix = 61  # cutout pixel size
        deltaPix = 0.05  # pixel size in arcsec (area per pixel = deltaPix**2)
        x, y, ra_at_xy_0, dec_at_xy_0, x_at_radec_0, y_at_radec_0, Mpix2coord, Mcoord2pix = util.make_grid_with_coordtransform(
            numPix=numPix, deltapix=deltaPix, subgrid_res=1, left_lower=False, inverse=False)
        flux = self.lightModel.surface_brightness(x, y, kwargs_list=self.kwargs_light)
        flux = util.array2image(flux)
        flux_max = np.max(flux)
        conv_pixels_partial = np.zeros((numPix, numPix), dtype=bool)
        conv_pixels_partial[flux >= flux_max / 20] = True
        self._conv_pixels_partial = conv_pixels_partial

        # high resolution ray-tracing and high resolution convolution, the full calculation
        self.kwargs_numerics_true = {'supersampling_factor': self._supersampling_factor,
                                # super sampling factor of (partial) high resolution ray-tracing
                                'compute_mode': 'regular',  # 'regular' or 'adaptive'
                                'supersampling_convolution': True,
                                # bool, if True, performs the supersampled convolution (either on regular or adaptive grid)
                                'supersampling_kernel_size': None,
                                # size of the higher resolution kernel region (can be smaller than the original kernel). None leads to use the full size
                                'flux_evaluate_indexes': None,  # bool mask, if None, it will evaluate all (sub) pixels
                                'supersampled_indexes': None,
                                # bool mask of pixels to be computed in supersampled grid (only for adaptive mode)
                                'compute_indexes': None,
                                # bool mask of pixels to be computed the PSF response (flux being added to). Only used for adaptive mode and can be set =likelihood mask.
                                'point_source_supersampling_factor': 1,
                                # int, supersampling factor when rendering a point source (not used in this script)
                                }

        # high resolution convolution on a smaller PSF with low resolution convolution on the edges of the PSF and high resolution ray tracing
        self.kwargs_numerics_high_res_narrow = {'supersampling_factor': self._supersampling_factor,
                                           'compute_mode': 'regular',
                                           'supersampling_convolution': True,
                                           'supersampling_kernel_size': 5,
                                           }

        # low resolution convolution based on high resolution ray-tracing grid
        self.kwargs_numerics_low_conv_high_grid = {'supersampling_factor': self._supersampling_factor,
                                              'compute_mode': 'regular',
                                              'supersampling_convolution': False,
                                              # does not matter for supersampling_factor=1
                                              'supersampling_kernel_size': None,
                                              # does not matter for supersampling_factor=1
                                              }

        # low resolution convolution with a subset of pixels with high resolution ray-tracing
        self.kwargs_numerics_low_conv_high_adaptive = {'supersampling_factor': self._supersampling_factor,
                                                  'compute_mode': 'adaptive',
                                                  'supersampling_convolution': False,
                                                  # does not matter for supersampling_factor=1
                                                  'supersampling_kernel_size': None,
                                                  # does not matter for supersampling_factor=1
                                                  'supersampled_indexes': self._conv_pixels_partial,
                                                       'convolution_kernel_size': 9,
                                                  }

        # low resolution convolution with a subset of pixels with high resolution ray-tracing and high resoluton convolution on smaller kernel size
        self.kwargs_numerics_high_adaptive = {'supersampling_factor': self._supersampling_factor,
                                         'compute_mode': 'adaptive',
                                         'supersampling_convolution': True,
                                         # does not matter for supersampling_factor=1
                                         'supersampling_kernel_size': 5,  # does not matter for supersampling_factor=1
                                         'supersampled_indexes': self._conv_pixels_partial,
                                              'convolution_kernel_size': 9,
                                         }

        # low resolution convolution and low resolution ray tracing, the simplest calculation
        self.kwargs_numerics_low_res = {'supersampling_factor': 1,
                                   'compute_mode': 'regular',
                                   'supersampling_convolution': False,  # does not matter for supersampling_factor=1
                                   'supersampling_kernel_size': None,  # does not matter for supersampling_factor=1
                                        'convolution_kernel_size': 9,
                                   }

        flux_evaluate_indexes = np.zeros((numPix, numPix), dtype=bool)
        flux_evaluate_indexes[flux >= flux_max / 1000] = True
        # low resolution convolution on subframe
        self.kwargs_numerics_partial = {'supersampling_factor': 1,
                                        'compute_mode': 'regular',
                                        'supersampling_convolution': False,
                                        # does not matter for supersampling_factor=1
                                        'supersampling_kernel_size': None,  # does not matter for supersampling_factor=1
                                        'flux_evaluate_indexes': flux_evaluate_indexes,
                                        'convolution_kernel_size': 9
                                        }


        # import PSF file
        kernel_super = kernel_util.kernel_gaussian(kernel_numPix=11 * self._supersampling_factor,
                                                                     deltaPix=deltaPix / self._supersampling_factor, fwhm=0.1)


        kernel_size = 9
        kernel_super = kernel_util.cut_psf(psf_data=kernel_super, psf_size=kernel_size * self._supersampling_factor)

        # make instance of the PixelGrid class
        from lenstronomy.Data.pixel_grid import PixelGrid
        kwargs_grid = {'nx': numPix, 'ny': numPix, 'transform_pix2angle': Mpix2coord, 'ra_at_xy_0': ra_at_xy_0,
                       'dec_at_xy_0': dec_at_xy_0}
        self.pixel_grid = PixelGrid(**kwargs_grid)

        # make instance of the PSF class
        from lenstronomy.Data.psf import PSF
        kwargs_psf = {'psf_type': 'PIXEL', 'kernel_point_source': kernel_super,
                      'point_source_supersampling_factor': self._supersampling_factor}
        self.psf_class = PSF(**kwargs_psf)



        # without convolution
        image_model_true = ImageModel(self.pixel_grid, self.psf_class, lens_light_model_class=self.lightModel,
                                      kwargs_numerics=self.kwargs_numerics_true)
        self.image_true = image_model_true.image(kwargs_lens_light=self.kwargs_light)
class MultiPatchReconstruction(MultiBandImageReconstruction):
    """
    this class illustrates the model of disconnected multi-patch modeling with 'joint-linear' option in one single
    array.
    """
    def __init__(self,
                 multi_band_list,
                 kwargs_model,
                 kwargs_params,
                 multi_band_type='joint-linear',
                 kwargs_likelihood=None,
                 kwargs_pixel_grid=None,
                 verbose=True):
        """

        :param multi_band_list: list of imaging data configuration [[kwargs_data, kwargs_psf, kwargs_numerics], [...]]
        :param kwargs_model: model keyword argument list
        :param kwargs_params: keyword arguments of the model parameters, same as output of FittingSequence() 'kwargs_result'
        :param multi_band_type: string, option when having multiple imaging data sets modelled simultaneously. Options are:
            - 'multi-linear': linear amplitudes are inferred on single data set
            - 'linear-joint': linear amplitudes ae jointly inferred
            - 'single-band': single band
        :param kwargs_likelihood: likelihood keyword arguments as supported by the Likelihood() class
        :param kwargs_pixel_grid: keyword argument of PixelGrid() class. This is optional and overwrites a minimal grid
         Attention for consistent pixel grid definitions!
        :param verbose: if True (default), computes and prints the total log-likelihood.
        This can deactivated for speedup purposes (does not run linear inversion again), and reduces the number of prints.
        """
        self._multi_band_list = multi_band_list
        if not multi_band_type == 'joint-linear':
            raise ValueError(
                'MultiPatchPlot only works with multi_band_type="joint_linear". '
                'Setting choice was %s. ' % multi_band_type)
        MultiBandImageReconstruction.__init__(
            self,
            multi_band_list,
            kwargs_model,
            kwargs_params,
            multi_band_type=multi_band_type,
            kwargs_likelihood=kwargs_likelihood,
            verbose=verbose)
        if kwargs_pixel_grid is not None:
            self._pixel_grid_joint = PixelGrid(**kwargs_pixel_grid)
        else:
            self._pixel_grid_joint = self._joint_pixel_grid(multi_band_list)

    @property
    def pixel_grid_joint(self):
        """

        :return: PixelGrid() class instance covering the entire window of the sky including all individual patches
        """
        return self._pixel_grid_joint

    @staticmethod
    def _joint_pixel_grid(multi_band_list):
        """
        Joint PixelGrid() class instance.
        This routine only works when the individual patches have the same coordinate system orientation and pixel scale.

        :param multi_band_list: list of imaging data configuration [[kwargs_data, kwargs_psf, kwargs_numerics], [...]]
        :return: PixelGrid() class instance covering the entire window of the sky including all individual patches
        """

        nx, ny = 0, 0
        kwargs_data = copy.deepcopy(multi_band_list[0][0])
        kwargs_pixel_grid = {
            'nx': 0,
            'ny': 0,
            'transform_pix2angle': kwargs_data['transform_pix2angle'],
            'ra_at_xy_0': kwargs_data['ra_at_xy_0'],
            'dec_at_xy_0': kwargs_data['dec_at_xy_0']
        }
        pixel_grid = PixelGrid(**kwargs_pixel_grid)
        Mpix2a = pixel_grid.transform_pix2angle

        # set up joint coordinate system and pixel size to include all frames
        for i in range(len(multi_band_list)):
            kwargs_data = multi_band_list[i][0]
            data_class_i = ImageData(**kwargs_data)
            Mpix2a_i = data_class_i.transform_pix2angle
            # check we are operating in the same coordinate system/rotation and pixel scale
            npt.assert_almost_equal(Mpix2a, Mpix2a_i, decimal=5)

            # evaluate pixel of zero point with the base coordinate system
            ra0, dec0 = data_class_i.radec_at_xy_0
            x_min, y_min = pixel_grid.map_coord2pix(ra0, dec0)
            nx_i, ny_i = data_class_i.num_pixel_axes
            nx, ny = _update_frame_size(nx, ny, x_min, y_min, nx_i, ny_i)

            # select minimum in x- and y-axis
            # transform back in RA/DEC and make this the new zero point of the base coordinate system
            ra_at_xy_0_new, dec_at_xy_0_new = pixel_grid.map_pix2coord(
                np.minimum(x_min, 0), np.minimum(y_min, 0))
            kwargs_pixel_grid['ra_at_xy_0'] = ra_at_xy_0_new
            kwargs_pixel_grid['dec_at_xy_0'] = dec_at_xy_0_new
            kwargs_pixel_grid['nx'] = nx
            kwargs_pixel_grid['ny'] = ny
            pixel_grid = PixelGrid(**kwargs_pixel_grid)
        return pixel_grid

    def image_joint(self):
        """
        patch together the individual patches of data and models

        :return: image_joint, model_joint, norm_residuals_joint
        """
        nx, ny = self._pixel_grid_joint.num_pixel_axes
        image_joint = np.zeros((ny, nx))
        model_joint = np.zeros((ny, nx))
        norm_residuals_joint = np.zeros((ny, nx))
        for model_band in self.model_band_list:
            if model_band is not None:
                image_model = model_band.image_model_class
                kwargs_params = model_band.kwargs_model
                model = image_model.image(**kwargs_params)
                data_class_i = image_model.Data
                # evaluate pixel of zero point with the base coordinate system
                ra0, dec0 = data_class_i.radec_at_xy_0
                x_min, y_min = self._pixel_grid_joint.map_coord2pix(ra0, dec0)
                nx_i, ny_i = data_class_i.num_pixel_axes
                image_joint[int(y_min):int(y_min + ny_i),
                            int(x_min):int(x_min + nx_i)] = data_class_i.data
                model_joint[int(y_min):int(y_min + ny_i),
                            int(x_min):int(x_min + nx_i)] = model
                norm_residuals_joint[
                    int(y_min):int(y_min + ny_i),
                    int(x_min):int(x_min + nx_i)] = model_band.norm_residuals
        return image_joint, model_joint, norm_residuals_joint

    def lens_model_joint(self):
        """
        patch together the individual patches of the lens model (can be discontinues)

        :return: 2d numpy arrays of kappa_joint, magnification_joint, alpha_x_joint, alpha_y_joint
        """
        nx, ny = self._pixel_grid_joint.num_pixel_axes
        kappa_joint = np.zeros((ny, nx))
        magnification_joint = np.zeros((ny, nx))
        alpha_x_joint, alpha_y_joint = np.zeros((ny, nx)), np.zeros((ny, nx))
        for model_band in self.model_band_list:
            if model_band is not None:
                image_model = model_band.image_model_class
                kwargs_params = model_band.kwargs_model
                kwargs_lens = kwargs_params['kwargs_lens']
                lens_model = image_model.LensModel
                x_grid, y_grid = image_model.Data.pixel_coordinates
                kappa = lens_model.kappa(x_grid, y_grid, kwargs_lens)
                magnification = lens_model.magnification(
                    x_grid, y_grid, kwargs_lens)
                alpha_x, alpha_y = lens_model.alpha(x_grid, y_grid,
                                                    kwargs_lens)

                data_class_i = image_model.Data
                # evaluate pixel of zero point with the base coordinate system
                ra0, dec0 = data_class_i.radec_at_xy_0
                x_min, y_min = self._pixel_grid_joint.map_coord2pix(ra0, dec0)
                nx_i, ny_i = data_class_i.num_pixel_axes
                kappa_joint[int(y_min):int(y_min + ny_i),
                            int(x_min):int(x_min + nx_i)] = kappa
                magnification_joint[int(y_min):int(y_min + ny_i),
                                    int(x_min):int(x_min +
                                                   nx_i)] = magnification
                alpha_x_joint[int(y_min):int(y_min + ny_i),
                              int(x_min):int(x_min + nx_i)] = alpha_x
                alpha_y_joint[int(y_min):int(y_min + ny_i),
                              int(x_min):int(x_min + nx_i)] = alpha_y
        return kappa_joint, magnification_joint, alpha_x_joint, alpha_y_joint

    def source(self, num_pix, delta_pix, center=None):
        """
        source in the same coordinate system as the image

        :param num_pix: number of pixels per axes
        :param delta_pix: pixel size
        :param center: list with two entries [center_x, center_y] (optional)
        :return: 2d surface brightness grid of the reconstructed source and PixelGrid() instance of source grid
        """
        Mpix2coord = self._pixel_grid_joint.transform_pix2angle * delta_pix / self._pixel_grid_joint.pixel_width
        x_grid_source, y_grid_source = util.make_grid_transformed(
            num_pix, Mpix2Angle=Mpix2coord)
        ra_at_xy_0, dec_at_xy_0 = x_grid_source[0], y_grid_source[0]

        image_model = self.model_band_list[0].image_model_class
        kwargs_model = self.model_band_list[0].kwargs_model
        kwargs_source = kwargs_model['kwargs_source']

        center_x = 0
        center_y = 0
        if center is not None:
            center_x, center_y = center[0], center[1]
        elif len(kwargs_source) > 0:
            center_x = kwargs_source[0]['center_x']
            center_y = kwargs_source[0]['center_y']
        x_grid_source += center_x
        y_grid_source += center_y

        pixel_grid = PixelGrid(nx=num_pix,
                               ny=num_pix,
                               transform_pix2angle=Mpix2coord,
                               ra_at_xy_0=ra_at_xy_0 + center_x,
                               dec_at_xy_0=dec_at_xy_0 + center_y)

        source = image_model.SourceModel.surface_brightness(
            x_grid_source, y_grid_source, kwargs_source)
        source = util.array2image(source) * delta_pix**2
        return source, pixel_grid