def __init__(self, kwargs_data): """ kwargs_data must contain: 'image_data': 2d numpy array of the image data 'transform_pix2angle' 2x2 transformation matrix (linear) to transform a pixel shift into a coordinate shift (x, y) -> (ra, dec) 'ra_at_xy_0' RA coordinate of pixel (0,0) 'dec_at_xy_0' DEC coordinate of pixel (0,0) optional keywords for shifts in the coordinate system: 'ra_shift': shifts the coordinate system with respect to 'ra_at_xy_0' 'dec_shift': shifts the coordinate system with respect to 'dec_at_xy_0' optional keywords for noise properties: 'background_rms': rms value of the background noise 'exp_time: float, exposure time to compute the Poisson noise contribution 'exposure_map': 2d numpy array, effective exposure time for each pixel. If set, will replace 'exp_time' :param kwargs_data: :param subgrid_res: :param psf_subgrid: """ if not 'image_data' in kwargs_data: if not 'numPix' in kwargs_data: raise ValueError( "keyword 'image_data' must be specified and consist of a 2d numpy array or at least 'numPix'!" ) else: numPix = kwargs_data['numPix'] data = np.zeros((numPix, numPix)) else: data = kwargs_data['image_data'] self.nx, self.ny = np.shape(data) if self.nx != self.ny: raise ValueError( "'image_data' with non-equal pixel number in x- and y-axis not yet supported!" ) ra_at_xy_0 = kwargs_data.get('ra_at_xy_0', 0) + kwargs_data.get( 'ra_shift', 0) dec_at_xy_0 = kwargs_data.get('dec_at_xy_0', 0) + kwargs_data.get( 'dec_shift', 0) transform_pix2angle = kwargs_data.get('transform_pix2angle', np.array([[1, 0], [0, 1]])) self._coords = Coordinates(transform_pix2angle=transform_pix2angle, ra_at_xy_0=ra_at_xy_0, dec_at_xy_0=dec_at_xy_0) self._x_grid, self._y_grid = self._coords.coordinate_grid(self.nx) if 'exposure_map' in kwargs_data: exp_map = kwargs_data['exposure_map'] exp_map[exp_map <= 0] = 10**(-10) else: exp_map = kwargs_data.get('exp_time', None) self._exp_map = exp_map self._data = data self._sigma_b = kwargs_data.get('background_rms', None)
def test_map_pix2coord(self): deltaPix = 0.05 Mpix2a = np.array([[1, 0], [0, 1]]) * deltaPix ra_0 = 1. dec_0 = 1. coords = Coordinates(transform_pix2angle=Mpix2a, ra_at_xy_0=ra_0, dec_at_xy_0=dec_0) x, y = coords.map_pix2coord(1, 0) assert x == deltaPix + ra_0 assert y == dec_0
def test_init(self): deltaPix = 0.05 Mpix2a = np.array([[1, 0], [0, 1]]) * deltaPix ra_0 = 1. dec_0 = 1. coords = Coordinates(transform_pix2angle=Mpix2a, ra_at_xy_0=ra_0, dec_at_xy_0=dec_0) ra, dec = coords.map_pix2coord(0, 0) assert ra == ra_0 assert dec == dec_0 x, y = coords.map_coord2pix(ra, dec) assert ra_0 == ra assert dec_0 == dec assert x == 0 assert y == 0
def test_coordinate_grid(self): deltaPix = 0.05 Mpix2a = np.array([[1, 0], [0, 1]]) * deltaPix ra_0 = 1. dec_0 = 1. coords = Coordinates(transform_pix2angle=Mpix2a, ra_at_xy_0=ra_0, dec_at_xy_0=dec_0) ra_grid, dec_grid = coords.coordinate_grid(numPix=10) assert ra_grid[0, 0] == ra_0 assert dec_grid[0, 0] == dec_0 x_pos, y_pos = 1, 2 ra, dec = coords.map_pix2coord(x_pos, y_pos) npt.assert_almost_equal(ra_grid[int(y_pos), int(x_pos)], ra, decimal=8) npt.assert_almost_equal(dec_grid[int(y_pos), int(x_pos)], dec, decimal=8)
def __init__(self, kwargs_data): """ :param kwargs_data: keyword arguments as described above """ if not 'image_data' in kwargs_data: if not 'numPix' in kwargs_data: raise ValueError( "keyword 'image_data' must be specified and consist of a 2d numpy array or at least 'numPix'!" ) else: numPix = kwargs_data['numPix'] data = np.zeros((numPix, numPix)) else: data = kwargs_data['image_data'] self.nx, self.ny = np.shape(data) if self.nx != self.ny: raise ValueError( "'image_data' with non-equal pixel number in x- and y-axis not yet supported!" ) ra_at_xy_0 = kwargs_data.get('ra_at_xy_0', 0) + kwargs_data.get( 'ra_shift', 0) dec_at_xy_0 = kwargs_data.get('dec_at_xy_0', 0) + kwargs_data.get( 'dec_shift', 0) transform_pix2angle = kwargs_data.get('transform_pix2angle', np.array([[1, 0], [0, 1]])) self._coords = Coordinates(transform_pix2angle=transform_pix2angle, ra_at_xy_0=ra_at_xy_0, dec_at_xy_0=dec_at_xy_0) self._x_grid, self._y_grid = self._coords.coordinate_grid(self.nx) if 'exposure_map' in kwargs_data: exp_map = kwargs_data['exposure_map'] exp_map[exp_map <= 0] = 10**(-10) else: exp_time = kwargs_data.get('exp_time', 1) exp_map = np.ones_like(data) * exp_time self._exp_map = exp_map self._data = data self._sigma_b = kwargs_data.get('background_rms', None) if 'noise_map' in kwargs_data: self._noise_map = kwargs_data['noise_map'] if self._noise_map is not None: self._sigma_b = 1 self._exp_map = np.ones_like(data) else: self._noise_map = None
def error_map_source_plot(self, ax, numPix, deltaPix_source, v_min=None, v_max=None, with_caustics=False): x_grid_source, y_grid_source = util.make_grid_transformed(numPix, self._Mpix2coord * deltaPix_source / self._deltaPix) x_center = self._kwargs_source[0]['center_x'] y_center = self._kwargs_source[0]['center_y'] x_grid_source += x_center y_grid_source += y_center coords_source = Coordinates(self._Mpix2coord * deltaPix_source / self._deltaPix, ra_at_xy_0=x_grid_source[0], dec_at_xy_0=y_grid_source[0]) error_map_source = self._analysis.error_map_source(self._kwargs_source, x_grid_source, y_grid_source, self._cov_param) error_map_source = util.array2image(error_map_source) d_s = numPix * deltaPix_source im = ax.matshow(error_map_source, origin='lower', extent=[0, d_s, 0, d_s], cmap=self._cmap, vmin=v_min, vmax=v_max) # source ax.get_xaxis().set_visible(False) ax.get_yaxis().set_visible(False) ax.autoscale(False) divider = make_axes_locatable(ax) cax = divider.append_axes("right", size="5%", pad=0.05) cb = plt.colorbar(im, cax=cax) cb.set_label(r'error variance', fontsize=15) if with_caustics: ra_caustic_list, dec_caustic_list = self._caustics() plot_line_set(ax, coords_source, ra_caustic_list, dec_caustic_list, color='b') scale_bar(ax, d_s, dist=0.1, text='0.1"', color='w', flipped=False) coordinate_arrows(ax, d_s, coords_source, arrow_size=self._arrow_size, color='w') text_description(ax, d_s, text="Error map in source", color="w", backgroundcolor='k', flipped=False) source_position_plot(ax, coords_source, self._kwargs_source) return ax
def test_plot_line_set(self): coords = Coordinates(transform_pix2angle=[[1, 0], [0, 1]], ra_at_xy_0=0, dec_at_xy_0=0) line_set_x = np.linspace(start=0, stop=1, num=10) line_set_y = np.linspace(start=0, stop=1, num=10) f, ax = plt.subplots(1, 1, figsize=(4, 4)) ax = plot_util.plot_line_set(ax, coords, line_set_x, line_set_y, origin=None, color='g', flipped_x=True, pixel_offset=False) plt.close() f, ax = plt.subplots(1, 1, figsize=(4, 4)) ax = plot_util.plot_line_set(ax, coords, line_set_x, line_set_y, origin=[1, 1], color='g', flipped_x=False, pixel_offset=True) plt.close() # and here we input a list of arrays line_set_list_x = [np.linspace(start=0, stop=1, num=10), np.linspace(start=0, stop=1, num=10)] line_set_list_y = [np.linspace(start=0, stop=1, num=10), np.linspace(start=0, stop=1, num=10)] f, ax = plt.subplots(1, 1, figsize=(4, 4)) ax = plot_util.plot_line_set(ax, coords, line_set_list_x, line_set_list_y, origin=None, color='g', flipped_x=True) plt.close() f, ax = plt.subplots(1, 1, figsize=(4, 4)) ax = plot_util.plot_line_set(ax, coords, line_set_list_x, line_set_list_y, origin=[1, 1], color='g', flipped_x=False) plt.close()
def test_source_position_plot(self): from lenstronomy.PointSource.point_source import PointSource from lenstronomy.LensModel.lens_model import LensModel lensModel = LensModel(lens_model_list=['SIS']) ps = PointSource(point_source_type_list=[ 'UNLENSED', 'LENSED_POSITION', 'SOURCE_POSITION' ], lensModel=lensModel) kwargs_lens = [{'theta_E': 1., 'center_x': 0, 'center_y': 0}] kwargs_ps = [{ 'ra_image': [1., 1.], 'dec_image': [0, 1], 'point_amp': [1, 1] }, { 'ra_image': [1.], 'dec_image': [1.], 'point_amp': [10] }, { 'ra_source': 0.1, 'dec_source': 0, 'point_amp': 1. }] ra_source, dec_source = ps.source_position(kwargs_ps, kwargs_lens) from lenstronomy.Data.coord_transforms import Coordinates coords_source = Coordinates( transform_pix2angle=np.array([[1, 0], [0, 1]]) * 0.1, ra_at_xy_0=-2, dec_at_xy_0=-2) f, ax = plt.subplots(1, 1, figsize=(4, 4)) plot_util.source_position_plot(ax, coords_source, ra_source, dec_source) plt.close()
def source(self, numPix, deltaPix, center=None, image_orientation=True): """ :param numPix: number of pixels per axes :param deltaPix: pixel size :param image_orientation: bool, if True, uses frame in orientation of the image, otherwise in RA-DEC coordinates :return: 2d surface brightness grid of the reconstructed source and Coordinates() instance of source grid """ if image_orientation is True: Mpix2coord = self._coords.transform_pix2angle * deltaPix / self._deltaPix x_grid_source, y_grid_source = util.make_grid_transformed( numPix, Mpix2Angle=Mpix2coord) ra_at_xy_0, dec_at_xy_0 = x_grid_source[0], y_grid_source[0] else: x_grid_source, y_grid_source, ra_at_xy_0, dec_at_xy_0, x_at_radec_0, y_at_radec_0, Mpix2coord, Mcoord2pix = util.make_grid_with_coordtransform( numPix, deltaPix) center_x = 0 center_y = 0 if center is not None: center_x, center_y = center[0], center[1] elif len(self._kwargs_source_partial) > 0: center_x = self._kwargs_source_partial[0]['center_x'] center_y = self._kwargs_source_partial[0]['center_y'] x_grid_source += center_x y_grid_source += center_y coords_source = Coordinates(transform_pix2angle=Mpix2coord, ra_at_xy_0=ra_at_xy_0 + center_x, dec_at_xy_0=dec_at_xy_0 + center_y) source = self._bandmodel.SourceModel.surface_brightness( x_grid_source, y_grid_source, self._kwargs_source_partial) source = util.array2image(source) * deltaPix**2 return source, coords_source
def test_image_position_plot(self): coords = Coordinates(transform_pix2angle=[[1, 0], [0, 1]], ra_at_xy_0=0, dec_at_xy_0=0) f, ax = plt.subplots(1, 1, figsize=(4, 4)) ra_image, dec_image = np.array([1, 2]), np.array([1, 2]) ax = plot_util.image_position_plot(ax, coords, ra_image, dec_image, color='w', image_name_list=None, origin=None, flipped_x=False) plt.close() ax = plot_util.image_position_plot(ax, coords, ra_image, dec_image, color='w', image_name_list=['A', 'B'], origin=[1, 1], flipped_x=True) plt.close()
def test_pixel_size(self): deltaPix = -0.05 Mpix2a = np.array([[1, 0], [0, 1]]) * deltaPix ra_0 = 1. dec_0 = 1. coords = Coordinates(transform_pix2angle=Mpix2a, ra_at_xy_0=ra_0, dec_at_xy_0=dec_0) deltaPix_out = coords.pixel_size assert deltaPix_out == -deltaPix
def test_rescaled_grid(self): import lenstronomy.Util.util as util numPix = 10 theta = 0.5 deltaPix = 0.05 subgrid_res = 3 Mpix2a = np.array([[np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)]]) * deltaPix x_grid, y_grid = util.make_grid_transformed(numPix, Mpix2a) coords = Coordinates(Mpix2a, ra_at_xy_0=x_grid[0], dec_at_xy_0=y_grid[0]) x_grid_high_res, y_grid_high_res = util.make_subgrid( x_grid, y_grid, subgrid_res=subgrid_res) coords_sub = Coordinates(Mpix2a / subgrid_res, ra_at_xy_0=x_grid_high_res[0], dec_at_xy_0=y_grid_high_res[0]) x, y = coords_sub.map_coord2pix(x_grid[1], y_grid[1]) npt.assert_almost_equal(x, 4, decimal=10) npt.assert_almost_equal(y, 1, decimal=10) x, y = coords_sub.map_coord2pix(x_grid[0], y_grid[0]) npt.assert_almost_equal(x, 1, decimal=10) npt.assert_almost_equal(y, 1, decimal=10) ra, dec = coords_sub.map_pix2coord(1, 1) npt.assert_almost_equal(ra, x_grid[0], decimal=10) npt.assert_almost_equal(dec, y_grid[0], decimal=10) ra, dec = coords_sub.map_pix2coord(1 + 2 * subgrid_res, 1) npt.assert_almost_equal(ra, x_grid[2], decimal=10) npt.assert_almost_equal(dec, y_grid[2], decimal=10) x_2d = util.array2image(x_grid) y_2d = util.array2image(y_grid) ra, dec = coords_sub.map_pix2coord(1 + 2 * subgrid_res, 1 + 3 * subgrid_res) npt.assert_almost_equal(ra, x_2d[3, 2], decimal=10) npt.assert_almost_equal(dec, y_2d[3, 2], decimal=10) ra, dec = coords.map_pix2coord(2, 3) npt.assert_almost_equal(ra, x_2d[3, 2], decimal=10) npt.assert_almost_equal(dec, y_2d[3, 2], decimal=10)
def test_xy_at_radec_0(self): deltaPix = 0.05 Mpix2a = np.array([[1, 0], [0, 1]]) * deltaPix ra_0 = 1. dec_0 = 1. coords = Coordinates(transform_pix2angle=Mpix2a, ra_at_xy_0=ra_0, dec_at_xy_0=dec_0) x_at_radec_0, y_at_radec_0 = coords.xy_at_radec_0 npt.assert_almost_equal(x_at_radec_0, -20, decimal=8) npt.assert_almost_equal(x_at_radec_0, -20, decimal=8) Ma2pix_ = coords.transform_angle2pix Ma2pix = linalg.inv(coords._Mpix2a) npt.assert_almost_equal(Ma2pix, Ma2pix_, decimal=8)
def error_map_source_plot(self, ax, numPix, deltaPix_source, v_min=None, v_max=None, with_caustics=False, font_size=15, point_source_position=True): """ plots the uncertainty in the surface brightness in the source from the linear inversion by taking the diagonal elements of the covariance matrix of the inversion of the basis set to be propagated to the source plane. #TODO illustration of the uncertainties in real space with the full covariance matrix is subtle. The best way is probably to draw realizations from the covariance matrix. :param ax: matplotlib axis instance :param numPix: number of pixels in plot per axis :param deltaPix_source: pixel spacing in the source resolution illustrated in plot :param v_min: minimum plotting scale of the map :param v_max: maximum plotting scale of the map :param with_caustics: plot the caustics on top of the source reconstruction (may take some time) :param font_size: font size of labels :param point_source_position: boolean, if True, plots a point at the position of the point source :return: plot of source surface brightness errors in the reconstruction on the axis instance """ x_grid_source, y_grid_source = util.make_grid_transformed(numPix, self._coords.transform_pix2angle * deltaPix_source / self._deltaPix) x_center = self._kwargs_source_partial[0]['center_x'] y_center = self._kwargs_source_partial[0]['center_y'] x_grid_source += x_center y_grid_source += y_center coords_source = Coordinates(self._coords.transform_pix2angle * deltaPix_source / self._deltaPix, ra_at_xy_0=x_grid_source[0], dec_at_xy_0=y_grid_source[0]) error_map_source = self.bandmodel.error_map_source(self._kwargs_source_partial, x_grid_source, y_grid_source, self._cov_param, model_index_select=False) error_map_source = util.array2image(error_map_source) d_s = numPix * deltaPix_source im = ax.matshow(error_map_source, origin='lower', extent=[0, d_s, 0, d_s], cmap=self._cmap, vmin=v_min, vmax=v_max) # source ax.get_xaxis().set_visible(False) ax.get_yaxis().set_visible(False) ax.autoscale(False) divider = make_axes_locatable(ax) cax = divider.append_axes("right", size="5%", pad=0.05) cb = plt.colorbar(im, cax=cax) cb.set_label(r'error variance', fontsize=font_size) if with_caustics: ra_caustic_list, dec_caustic_list = self._caustics() plot_util.plot_line_set(ax, coords_source, ra_caustic_list, dec_caustic_list, color='b') plot_util.scale_bar(ax, d_s, dist=0.1, text='0.1"', color='w', flipped=False, font_size=font_size) plot_util.coordinate_arrows(ax, d_s, coords_source, arrow_size=self._arrow_size, color='w', font_size=font_size) plot_util.text_description(ax, d_s, text="Error map in source", color="w", backgroundcolor='k', flipped=False, font_size=font_size) if point_source_position is True: ra_source, dec_source = self.bandmodel.PointSource.source_position(self._kwargs_ps_partial, self._kwargs_lens_partial) plot_util.source_position_plot(ax, coords_source, ra_source, dec_source) return ax
def source_plot(self, ax, numPix, deltaPix_source, source_sigma=0.001, convolution=False, v_min=None, v_max=None, with_caustics=False): """ :param ax: :param coords_source: :param source: :return: """ if v_min is None: v_min = self._v_min_default if v_max is None: v_max = self._v_max_default d_s = numPix * deltaPix_source x_grid_source, y_grid_source = util.make_grid_transformed(numPix, self._Mpix2coord * deltaPix_source / self._deltaPix) if len(self._kwargs_source) > 0: x_center = self._kwargs_source[0]['center_x'] y_center = self._kwargs_source[0]['center_y'] x_grid_source += x_center y_grid_source += y_center coords_source = Coordinates(self._Mpix2coord * deltaPix_source / self._deltaPix, ra_at_xy_0=x_grid_source[0], dec_at_xy_0=y_grid_source[0]) source = self._imageModel.SourceModel.surface_brightness(x_grid_source, y_grid_source, self._kwargs_source) source = util.array2image(source) if convolution is True: source = ndimage.filters.gaussian_filter(source, sigma=source_sigma / deltaPix_source, mode='nearest', truncate=20) im = ax.matshow(np.log10(source), origin='lower', extent=[0, d_s, 0, d_s], cmap=self._cmap, vmin=v_min, vmax=v_max) # source ax.get_xaxis().set_visible(False) ax.get_yaxis().set_visible(False) ax.autoscale(False) divider = make_axes_locatable(ax) cax = divider.append_axes("right", size="5%", pad=0.05) cb = plt.colorbar(im, cax=cax) cb.set_label(r'log$_{10}$ flux', fontsize=15) if with_caustics: ra_caustic_list, dec_caustic_list = self._caustics() plot_line_set(ax, coords_source, ra_caustic_list, dec_caustic_list, color='b') scale_bar(ax, d_s, dist=0.1, text='0.1"', color='w', flipped=False) coordinate_arrows(ax, d_s, coords_source, arrow_size=self._arrow_size, color='w') text_description(ax, d_s, text="Reconstructed source", color="w", backgroundcolor='k', flipped=False) source_position_plot(ax, coords_source, self._kwargs_source) return ax
def test_shift_coordinate_system(self): deltaPix = 0.05 Mpix2a = np.array([[1, 0], [0, 1]]) * deltaPix ra_0 = 1. dec_0 = 1. coords = Coordinates(transform_pix2angle=Mpix2a, ra_at_xy_0=ra_0, dec_at_xy_0=dec_0) x0, y0 = coords.xy_at_radec_0 coords.shift_coordinate_system(x_shift=deltaPix, y_shift=0, pixel_unit=False) x0_new, y0_new = coords.xy_at_radec_0 assert x0_new == x0 - 1 coords = Coordinates(transform_pix2angle=Mpix2a, ra_at_xy_0=ra_0, dec_at_xy_0=dec_0) x0, y0 = coords.xy_at_radec_0 coords.shift_coordinate_system(x_shift=1, y_shift=0, pixel_unit=True) x0_new, y0_new = coords.xy_at_radec_0 assert x0_new == x0 - 1
class Data(object): """ class to handle the data, coordinate system and masking, including convolution with various numerical precisions The Data() class is initialized with keyword arguments: - 'image_data': 2d numpy array of the image data - 'transform_pix2angle' 2x2 transformation matrix (linear) to transform a pixel shift into a coordinate shift (x, y) -> (ra, dec) - 'ra_at_xy_0' RA coordinate of pixel (0,0) - 'dec_at_xy_0' DEC coordinate of pixel (0,0) optional keywords for shifts in the coordinate system: - 'ra_shift': shifts the coordinate system with respect to 'ra_at_xy_0' - 'dec_shift': shifts the coordinate system with respect to 'dec_at_xy_0' optional keywords for noise properties: - 'background_rms': rms value of the background noise - 'exp_time': float, exposure time to compute the Poisson noise contribution - 'exposure_map': 2d numpy array, effective exposure time for each pixel. If set, will replace 'exp_time' - 'noise_map': Gaussian noise (1-sigma) for each individual pixel. If this keyword is set, the other noise properties will be ignored. Notes: ------ the likelihood for the data given model P(data|model) is defined in the function below. Please make sure that your definitions and units of 'exposure_map', 'background_rms' and 'image_data' are in accordance with the likelihood function. In particular, make sure that the Poisson noise contribution is defined in the count rate. """ def __init__(self, kwargs_data): """ :param kwargs_data: keyword arguments as described above """ if not 'image_data' in kwargs_data: if not 'numPix' in kwargs_data: raise ValueError( "keyword 'image_data' must be specified and consist of a 2d numpy array or at least 'numPix'!" ) else: numPix = kwargs_data['numPix'] data = np.zeros((numPix, numPix)) else: data = kwargs_data['image_data'] self.nx, self.ny = np.shape(data) if self.nx != self.ny: raise ValueError( "'image_data' with non-equal pixel number in x- and y-axis not yet supported!" ) ra_at_xy_0 = kwargs_data.get('ra_at_xy_0', 0) + kwargs_data.get( 'ra_shift', 0) dec_at_xy_0 = kwargs_data.get('dec_at_xy_0', 0) + kwargs_data.get( 'dec_shift', 0) transform_pix2angle = kwargs_data.get('transform_pix2angle', np.array([[1, 0], [0, 1]])) self._coords = Coordinates(transform_pix2angle=transform_pix2angle, ra_at_xy_0=ra_at_xy_0, dec_at_xy_0=dec_at_xy_0) self._x_grid, self._y_grid = self._coords.coordinate_grid(self.nx) if 'exposure_map' in kwargs_data: exp_map = kwargs_data['exposure_map'] exp_map[exp_map <= 0] = 10**(-10) else: exp_time = kwargs_data.get('exp_time', 1) exp_map = np.ones_like(data) * exp_time self._exp_map = exp_map self._data = data self._sigma_b = kwargs_data.get('background_rms', None) if 'noise_map' in kwargs_data: self._noise_map = kwargs_data['noise_map'] if self._noise_map is not None: self._sigma_b = 1 self._exp_map = np.ones_like(data) else: self._noise_map = None def update_data(self, image_data): """ update the data :param image_data: 2d numpy array of same size as nx, ny :return: None """ nx, ny = np.shape(image_data) if not self.nx == nx and not self.ny == ny: raise ValueError( "shape of new data %s %s must equal old data %s %s!" % (nx, ny, self.nx, self.ny)) self._data = image_data def shift_coordinate_grid(self, x_shift, y_shift, pixel_unit=False): """ shifts the coordinate system :param x_shif: shift in x (or RA) :param y_shift: shift in y (or DEC) :param pixel_unit: bool, if True, units of pixels in input, otherwise RA/DEC :return: updated data class with change in coordinate system """ self._coords.shift_coordinate_grid(x_shift, y_shift, pixel_unit=pixel_unit) self._x_grid, self._y_grid = self._coords.coordinate_grid(self.nx) @property def data(self): """ :return: 2d numpy array of data """ return self._data @property def deltaPix(self): """ :return: pixel size (in units of arcsec) """ return self._coords.pixel_size @property def width(self): """ :return: width of data frame """ return len(self.data) * self.deltaPix @property def center(self): """ :return: center_x, center_y of coordinate system """ return np.mean(self._x_grid), np.mean(self._y_grid) @property def background_rms(self): """ :return: rms value of background noise """ if self._sigma_b is None: if self._noise_map is None: raise ValueError( "rms background value as 'background_rms' not specified!") return self._sigma_b @property def exposure_map(self): """ Units of data and exposure map should result in: number of flux counts = data * exposure_map :return: exposure map for each pixel """ if self._exp_map is None: if self._noise_map is None: raise ValueError( "Exposure map has not been specified in Data() class!") else: return self._exp_map @property def noise_map(self): """ 1-sigma error for each pixel (optional) :return: """ return self._noise_map @property def C_D(self): """ Covariance matrix of all pixel values in 2d numpy array (only diagonal component) The covariance matrix is estimated from the data. WARNING: For low count statistics, the noise in the data may lead to biased estimates of the covariance matrix. :return: covariance matrix of all pixel values in 2d numpy array (only diagonal component). """ if not hasattr(self, '_C_D'): if self._noise_map is not None: self._C_D = self._noise_map**2 else: self._C_D = self.covariance_matrix(self.data, self.background_rms, self.exposure_map) return self._C_D @property def numData(self): """ :return: number of pixels in the data """ nx, ny = np.shape(self._x_grid) return nx * ny @property def coordinates(self): """ :return: ra and dec coordinates of the pixels, each in 1d numpy arrays """ return self._x_grid, self._y_grid def map_coord2pix(self, ra, dec): """ maps the (ra,dec) coordinates of the system into the pixel coordinate of the image :param ra: relative RA coordinate as defined by the coordinate frame :param dec: relative DEC coordinate as defined by the coordinate frame :return: (x, y) pixel coordinates """ return self._coords.map_coord2pix(ra, dec) def map_pix2coord(self, x, y): """ maps the (x,y) pixel coordinates of the image into the system coordinates :param x: pixel coordinate (can be 1d numpy array), defined in the center of the pixel :param y: pixel coordinate (can be 1d numpy array), defined in the center of the pixel :return: relative (RA, DEC) coordinates of the system """ return self._coords.map_pix2coord(x, y) def covariance_matrix(self, data, background_rms=1, exposure_map=1, noise_map=None, verbose=False): """ returns a diagonal matrix for the covariance estimation which describes the error Notes: - the exposure map must be positive definite. Values that deviate too much from the mean exposure time will be given a lower limit to not under-predict the Poisson component of the noise. - the data must be positive semi-definite for the Poisson noise estimate. Values < 0 (Possible after mean subtraction) will not have a Poisson component in their noise estimate. :param data: data array, eg in units of photons/second :param background_rms: background noise rms, eg. in units (photons/second)^2 :param exposure_map: exposure time per pixel, e.g. in units of seconds :return: len(d) x len(d) matrix that give the error of background and Poisson components; (photons/second)^2 """ if noise_map is not None: return noise_map**2 if isinstance(exposure_map, int) or isinstance(exposure_map, float): if exposure_map <= 0: exposure_map = 1 else: mean_exp_time = np.mean(exposure_map) exposure_map[exposure_map < mean_exp_time / 10] = mean_exp_time / 10 if verbose: if background_rms * np.max(exposure_map) < 1: print( "WARNING! sigma_b*f %s < 1 count may introduce unstable error estimates" % (background_rms * np.max(exposure_map))) d_pos = np.zeros_like(data) #threshold = 1.5*sigma_b d_pos[data >= 0] = data[data >= 0] #d_pos[d < threshold] = 0 sigma = d_pos / exposure_map + background_rms**2 return sigma def log_likelihood(self, model, mask, additional_error_map=0): """ computes the likelihood of the data given the model p(data|model) The Gaussian errors are estimated with the covariance matrix, based on the model image. The errors include the background rms value and the exposure time to compute the Poisson noise level (in Gaussian approximation). :param model: the model (same dimensions and units as data) :param mask: bool (1, 0) values per pixel. If =0, the pixel is ignored in the likelihood :param additional_error_map: additional error term (in same units as covariance matrix). This can e.g. come from model errors in the PSF estimation. :return: the natural logarithm of the likelihood p(data|model) """ C_D = self.covariance_matrix(model, self._sigma_b, self.exposure_map, self.noise_map) X2 = (model - self._data)**2 / (C_D + np.abs(additional_error_map)) * mask X2 = np.array(X2) logL = -np.sum(X2) / 2 return logL
def get_masks(self): """ Create masks. :return: :rtype: """ if 'mask' in self.settings: if self.settings['mask'] is not None: if 'provided' in self.settings['mask'] \ and self.settings['mask']['provided'] is not None: return self.settings['mask']['provided'] else: masks = [] mask_options = deepcopy(self.settings['mask']) for n in range(self.band_number): ra_at_xy_0 = mask_options['ra_at_xy_0'][n] dec_at_xy_0 = mask_options['dec_at_xy_0'][n] transform_pix2angle = np.array( mask_options['transform_matrix'][n] ) num_pixel = mask_options['size'][n] radius = mask_options['radius'][n] offset = mask_options['centroid_offset'][n] coords = Coordinates(transform_pix2angle, ra_at_xy_0, dec_at_xy_0) x_coords, y_coords = coords.coordinate_grid(num_pixel, num_pixel) mask_outer = mask_util.mask_center_2d( self.deflector_center_ra + offset[0], self.deflector_center_dec + offset[1], radius, util.image2array(x_coords), util.image2array(y_coords) ) extra_masked_regions = [] try: self.settings['mask']['extra_regions'] except (NameError, KeyError): pass else: if self.settings['mask']['extra_regions'] is \ not None: for reg in self.settings['mask'][ 'extra_regions'][n]: extra_masked_regions.append( mask_util.mask_center_2d( self.deflector_center_ra + reg[0], self.deflector_center_dec + reg[1], reg[2], util.image2array(x_coords), util.image2array(y_coords) ) ) mask = 1. - mask_outer for extra_region in extra_masked_regions: mask *= extra_region # Mask Edge Pixels try: self.settings['mask']['mask_edge_pixels'] except (NameError, KeyError): pass else: border_length = \ self.settings['mask']['mask_edge_pixels'][n] if border_length > 0: edge_mask = 0 * np.ones( (num_pixel, num_pixel), dtype=int) edge_mask[border_length:-border_length, border_length:-border_length] = 1 edge_mask = (edge_mask.flatten()).tolist() elif border_length == 0: edge_mask = 1 * np.ones( (num_pixel, num_pixel), dtype=int) edge_mask = (edge_mask.flatten()).tolist() mask *= edge_mask # Add custom Mask try: self.settings['mask']['custom_mask'] except (NameError, KeyError): pass else: if self.settings['mask']['custom_mask'][n]\ is not None: provided_mask = \ self.settings['mask']['custom_mask'][n] provided_mask = np.array(provided_mask) # make sure that mask consist of only 0 and 1 provided_mask[provided_mask > 0.] = 1. provided_mask[provided_mask <= 0.] = 0. mask *= provided_mask # sanity check mask[mask >= 1.] = 1. mask[mask <= 0.] = 0. masks.append(util.array2image(mask)) return masks return None
class Data(object): """ class to handle the data, coordinate system and masking, including convolution with various numerical precisions """ def __init__(self, kwargs_data): """ kwargs_data must contain: 'image_data': 2d numpy array of the image data 'transform_pix2angle' 2x2 transformation matrix (linear) to transform a pixel shift into a coordinate shift (x, y) -> (ra, dec) 'ra_at_xy_0' RA coordinate of pixel (0,0) 'dec_at_xy_0' DEC coordinate of pixel (0,0) optional keywords for shifts in the coordinate system: 'ra_shift': shifts the coordinate system with respect to 'ra_at_xy_0' 'dec_shift': shifts the coordinate system with respect to 'dec_at_xy_0' optional keywords for noise properties: 'background_rms': rms value of the background noise 'exp_time: float, exposure time to compute the Poisson noise contribution 'exposure_map': 2d numpy array, effective exposure time for each pixel. If set, will replace 'exp_time' :param kwargs_data: :param subgrid_res: :param psf_subgrid: """ if not 'image_data' in kwargs_data: if not 'numPix' in kwargs_data: raise ValueError( "keyword 'image_data' must be specified and consist of a 2d numpy array or at least 'numPix'!" ) else: numPix = kwargs_data['numPix'] data = np.zeros((numPix, numPix)) else: data = kwargs_data['image_data'] self.nx, self.ny = np.shape(data) if self.nx != self.ny: raise ValueError( "'image_data' with non-equal pixel number in x- and y-axis not yet supported!" ) ra_at_xy_0 = kwargs_data.get('ra_at_xy_0', 0) + kwargs_data.get( 'ra_shift', 0) dec_at_xy_0 = kwargs_data.get('dec_at_xy_0', 0) + kwargs_data.get( 'dec_shift', 0) transform_pix2angle = kwargs_data.get('transform_pix2angle', np.array([[1, 0], [0, 1]])) self._coords = Coordinates(transform_pix2angle=transform_pix2angle, ra_at_xy_0=ra_at_xy_0, dec_at_xy_0=dec_at_xy_0) self._x_grid, self._y_grid = self._coords.coordinate_grid(self.nx) if 'exposure_map' in kwargs_data: exp_map = kwargs_data['exposure_map'] exp_map[exp_map <= 0] = 10**(-10) else: exp_map = kwargs_data.get('exp_time', None) self._exp_map = exp_map self._data = data self._sigma_b = kwargs_data.get('background_rms', None) def constructor_kwargs(self): """ :return: kwargs that allow to construct the Data() class """ kwargs_data = { 'numPix': self.nx, 'image_data': self.data, 'exposure_map': self._exp_map, 'background_rms': self._sigma_b, 'ra_at_xy_0': self._coords._ra_at_xy_0, 'dec_at_xy_0': self._coords._dec_at_xy_0, 'transform_pix2angle': self._coords._Mpix2a } return kwargs_data def update_data(self, image_data): """ update the data :param image_data: 2d numpy array of same size as nx, ny :return: """ nx, ny = np.shape(image_data) if not self.nx == nx and not self.ny == ny: raise ValueError( "shape of new data %s %s must equal old data %s %s!" % (nx, ny, self.nx, self.ny)) self._data = image_data @property def data(self): """ :return: 2d numpy array of data """ return self._data @property def deltaPix(self): """ :return: pixel size (in units of arcsec) """ return self._coords.pixel_size @property def background_rms(self): """ :return: rms value of background noise """ if self._sigma_b is None: raise ValueError( "rms background value as 'background_rms' not specified!") return self._sigma_b @property def exposure_map(self): """ :return: """ if self._exp_map is None: raise ValueError( "Exposure map has not been specified in Data() class!") else: return self._exp_map @property def C_D(self): """ :return: covariance matrix of all pixel values in 2d numpy array """ if not hasattr(self, '_C_D'): self._C_D = self.covariance_matrix(self.data, self.background_rms, self.exposure_map) return self._C_D @property def numData(self): return len(self._x_grid) @property def coordinates(self): return self._x_grid, self._y_grid def map_coord2pix(self, ra, dec): """ :param ra: :param dec: :return: """ return self._coords.map_coord2pix(ra, dec) def map_pix2coord(self, x, y): """ :param x: :param y: :return: """ return self._coords.map_pix2coord(x, y) def covariance_matrix(self, d, sigma_b, f, verbose=False): """ returns a diagonal matrix for the covariance estimation :param d: data array :param sigma_b: background noise :param f: reduced poissonian noise :return: len(d) x len(d) matrix """ if isinstance(f, int) or isinstance(f, float): if f <= 0: f = 1 else: mean_exp_time = np.mean(f) f[f < mean_exp_time / 10] = mean_exp_time / 10 if verbose: if sigma_b * np.max(f) < 1: print( "WARNING! sigma_b*f %s >1 may introduce unstable error estimates" % (sigma_b * np.max(f))) d_pos = np.zeros_like(d) #threshold = 1.5*sigma_b d_pos[d >= 0] = d[d >= 0] #d_pos[d < threshold] = 0 sigma = d_pos / f + sigma_b**2 return sigma def log_likelihood(self, model, mask, error_map=0): """ returns reduced residual map :param model: :param data: :param sigma: :param reduce_frac: :param mask: :param error_map: :return: """ C_D = self.covariance_matrix(model, self._sigma_b, self.exposure_map) X2 = (model - self._data)**2 / (C_D + np.abs(error_map)) * mask X2 = np.array(X2) logL = -np.sum(X2) / 2 return logL
def get_masks(self): """ Create masks. :return: :rtype: """ if 'mask' in self.settings: if self.settings['mask'] is not None: if 'provided' in self.settings['mask'] \ and self.settings['mask']['provided'] is not None: return self.settings['mask']['provided'] else: masks = [] mask_options = deepcopy(self.settings['mask']) for n in range(self.band_number): ra_at_xy_0 = mask_options['ra_at_xy_0'][n] dec_at_xy_0 = mask_options['dec_at_xy_0'][n] transform_pix2angle = np.array( mask_options['transform_matrix'][n] ) num_pixel = mask_options['size'][n] radius = mask_options['radius'][n] offset = mask_options['centroid_offset'][n] coords = Coordinates(transform_pix2angle, ra_at_xy_0, dec_at_xy_0) x_coords, y_coords = coords.coordinate_grid(num_pixel, num_pixel) mask_outer = mask_util.mask_center_2d( self.deflector_center_ra + offset[0], self.deflector_center_dec + offset[1], radius, util.image2array(x_coords), util.image2array(y_coords) ) extra_masked_regions = [] try: self.settings['mask']['extra_regions'] except (NameError, KeyError): pass else: if self.settings['mask']['extra_regions'] is \ not None: for reg in self.settings['mask'][ 'extra_regions'][n]: extra_masked_regions.append( mask_util.mask_center_2d( self.deflector_center_ra + reg[0], self.deflector_center_dec + reg[1], reg[2], util.image2array(x_coords), util.image2array(y_coords) ) ) mask = 1. - mask_outer for extra_region in extra_masked_regions: mask *= extra_region # sanity check mask[mask >= 1.] = 1. mask[mask <= 0.] = 0. masks.append(util.array2image(mask)) return masks return None