def cutout_psf_single(x, y, image, mask, kernel_size, kernel_init): """ :param x: x-coordinate of point source :param y: y-coordinate of point source :param image: image (i.e. data - all models subtracted, except a single point source) :param mask: mask of pixels in the image not to be considered in the PSF estimate (being replaced by kernel_init) :param kernel_size: width in pixel of the kernel :param kernel_init: initial guess of kernel (pixels that are masked are replaced by those values) :return: estimate of the PSF based on the image and position of the point source """ # cutout the star x_int = int(round(x)) y_int = int(round(y)) star_cutout = kernel_util.cutout_source(x_int, y_int, image, kernel_size + 2, shift=False) # cutout the mask mask_cutout = kernel_util.cutout_source(x_int, y_int, mask, kernel_size + 2, shift=False) # enlarge the initial PSF kernel to the new cutout size kernel_enlarged = np.zeros((kernel_size + 2, kernel_size + 2)) kernel_enlarged[1:-1, 1:-1] = kernel_init # shift the initial kernel to the shift of the star shift_x = x_int - x shift_y = y_int - y kernel_shifted = ndimage.shift(kernel_enlarged, shift=[-shift_y, -shift_x], order=1) # compute normalization of masked and unmasked region of the shifted kernel # norm_masked = np.sum(kernel_shifted[mask_i == 0]) norm_unmasked = np.sum(kernel_shifted[mask_cutout == 1]) # normalize star within the unmasked region to the norm of the initial kernel of the same region star_cutout /= np.sum(star_cutout[mask_cutout == 1]) * norm_unmasked # replace mask with shifted initial kernel (+2 size) star_cutout[mask_cutout == 0] = kernel_shifted[mask_cutout == 0] star_cutout[star_cutout < 0] = 0 # de-shift kernel kernel_deshifted = kernel_util.de_shift_kernel( star_cutout, shift_x, shift_y, iterations=20, fractional_step_size=0.1) # re-size kernel kernel_deshifted = image_util.cut_edges(kernel_deshifted, kernel_size) # re-normalize kernel again kernel_deshifted = kernel_util.kernel_norm(kernel_deshifted) return kernel_deshifted
def error_map_estimate(self, kernel, star_cutout_list, amp, x_pos, y_pos): """ provides a psf_error_map based on the goodness of fit of the given PSF kernel on the point source cutouts, their estimated amplitudes and positions :param kernel: PSF kernel :param star_cutout_list: list of 2d arrays of cutouts of the point sources with all other model components subtracted :param amp: list of amplitudes of the estimated PSF kernel :param x_pos: pixel position (in original data unit, not in cutout) of the point sources (same order as amp and star cutouts) :param y_pos: pixel position (in original data unit, not in cutout) of the point sources (same order as amp and star cutouts) :return: relative uncertainty in the psf model (in quadrature) per pixel based on residuals achieved in the image """ error_map_list = np.zeros( (len(star_cutout_list), len(kernel), len(kernel))) for i, star in enumerate(star_cutout_list): x, y, amp_i = x_pos[i], y_pos[i], amp[i] # shift kernel x_int = int(round(x)) y_int = int(round(y)) shift_x = x_int - x shift_y = y_int - y kernel_shifted = interp.shift(kernel, [-shift_y, -shift_x], order=1) # multiply kernel with amplitude model = kernel_shifted * amp_i # compute residuals residual = np.abs(star - model) # subtract background and Poisson noise residuals C_D_cutout = kernel_util.cutout_source( x_int, y_int, self._image_model_class.Data.C_D, len(star), shift=False) mask = kernel_util.cutout_source( x_int, y_int, self._image_model_class.likelihood_mask, len(star), shift=False) residual -= np.sqrt(C_D_cutout) residual[residual < 0] = 0 # estimate relative error per star residual /= amp_i error_map_list[i, :, :] = residual**2 * mask # take median absolute error for each pixel error_map = np.median(error_map_list, axis=0) error_map[kernel > 0] /= kernel[kernel > 0]**2 return error_map
def cutout_psf(self, ra_image, dec_image, x, y, image_list, kernelsize, kernel_init, block_center_neighbour=0): """ :param x_: :param y_: :param image_list: list of images (i.e. data - all models subtracted, except a single point source) :param kernelsize: :return: """ mask = self._image_model_class.likelihood_mask ra_grid, dec_grid = self._image_model_class.Data.pixel_coordinates ra_grid = util.image2array(ra_grid) dec_grid = util.image2array(dec_grid) radius = block_center_neighbour kernel_list = [] star_cutout_list = [] for l in range(len(x)): mask_point_source = self.mask_point_source(ra_image, dec_image, ra_grid, dec_grid, radius, i=l) mask_i = mask * mask_point_source kernel_deshifted = self.cutout_psf_single(x[l], y[l], image_list[l], mask_i, kernelsize, kernel_init) kernel_list.append(kernel_deshifted) x_int = int(round(x[l])) y_int = int(round(y[l])) star_cutout = kernel_util.cutout_source(x_int, y_int, image_list[l], kernelsize, shift=False) star_cutout_list.append(star_cutout) return kernel_list, star_cutout_list
def error_map_estimate(self, kernel, star_cutout_list, amp, x_pos, y_pos, error_map_radius=None): """ provides a psf_error_map based on the goodness of fit of the given PSF kernel on the point source cutouts, their estimated amplitudes and positions :param kernel: PSF kernel :param star_cutout_list: list of 2d arrays of cutouts of the point sources with all other model components subtracted :param amp: list of amplitudes of the estimated PSF kernel :param x_pos: pixel position (in original data unit, not in cutout) of the point sources (same order as amp and star cutouts) :param y_pos: pixel position (in original data unit, not in cutout) of the point sources (same order as amp and star cutouts) :param error_map_radius: float, radius (in arc seconds) of the outermost error in the PSF estimate (e.g. to avoid double counting of overlapping PSF erros) :return: relative uncertainty in the psf model (in quadrature) per pixel based on residuals achieved in the image """ error_map_list = np.zeros((len(star_cutout_list), len(kernel), len(kernel))) for i, star in enumerate(star_cutout_list): x, y, amp_i = x_pos[i], y_pos[i], amp[i] # shift kernel x_int = int(round(x)) y_int = int(round(y)) shift_x = x_int - x shift_y = y_int - y kernel_shifted = interp.shift(kernel, [-shift_y, -shift_x], order=1) # multiply kernel with amplitude model = kernel_shifted * amp_i # compute residuals residual = np.abs(star - model) # subtract background and Poisson noise residuals C_D_cutout = kernel_util.cutout_source(x_int, y_int, self._image_model_class.Data.C_D, len(star), shift=False) mask = kernel_util.cutout_source(x_int, y_int, self._image_model_class.likelihood_mask, len(star), shift=False) residual -= np.sqrt(C_D_cutout) residual[residual < 0] = 0 # estimate relative error per star residual /= amp_i error_map_list[i, :, :] = residual**2*mask # take median absolute error for each pixel error_map = np.median(error_map_list, axis=0) error_map[kernel > 0] /= kernel[kernel > 0]**2 error_map = np.nan_to_num(error_map) error_map[error_map > 1] = 1 # cap on error to be the same # mask the error map outside a certain radius (can avoid double counting of errors when map is overlapping if error_map_radius is not None: pixel_scale = self._image_model_class.Data.pixel_width x_grid, y_grid = util.make_grid(numPix=len(error_map), deltapix=pixel_scale) mask = mask_util.mask_azimuthal(x_grid, y_grid, center_x=0, center_y=0, r=error_map_radius) error_map *= util.array2image(mask) return error_map
def test_raise(self): with self.assertRaises(ValueError): kernel = np.zeros((2, 2)) kernel_util.center_kernel(kernel, iterations=1) with self.assertRaises(ValueError): kernel_super = np.ones((9, 9)) kernel_util.split_kernel(kernel_super, supersampling_kernel_size=2, supersampling_factor=3) with self.assertRaises(ValueError): kernel_util.split_kernel(kernel_super, supersampling_kernel_size=3, supersampling_factor=0) with self.assertRaises(ValueError): image = np.ones((10, 10)) kernel_util.cutout_source(x_pos=3, y_pos=2, image=image, kernelsize=2) with self.assertRaises(ValueError): kernel_util.fwhm_kernel(kernel=np.ones((4, 4))) with self.assertRaises(ValueError): kernel_util.fwhm_kernel(kernel=np.ones((5, 5)))
def test_cutout_source2(): grid2d = np.zeros((20, 20)) grid2d[7:9, 7:9] = 1 kernel = kernel_util.cutout_source(x_pos=7.5, y_pos=7.5, image=grid2d, kernelsize=5, shift=False) assert kernel[2, 2] == 1
def test_cutout_source_border(): kernel_size = 7 image = np.zeros((10, 10)) kernel = np.zeros((kernel_size, kernel_size)) kernel[2, 2] = 1 shift_x = +0.1 shift_y = 0 x_c, y_c = 2, 5 x_pos = x_c + shift_x y_pos = y_c + shift_y #kernel_shifted = interp.shift(kernel, [shift_y, shift_x], order=1) image = image_util.add_layer2image(image, x_pos, y_pos, kernel, order=1) kernel_new = kernel_util.cutout_source(x_pos=x_pos, y_pos=y_pos, image=image, kernelsize=kernel_size) nx_new, ny_new = np.shape(kernel_new) print(kernel_new) assert nx_new == kernel_size assert ny_new == kernel_size npt.assert_almost_equal(kernel_new[2, 2], kernel[2, 2], decimal=2)
def test_cutout_source(): """ test whether a shifted psf can be reproduced sufficiently well :return: """ kernel_size = 5 image = np.zeros((10, 10)) kernel = np.zeros((kernel_size, kernel_size)) kernel[2, 2] = 1 shift_x = 0.5 shift_y = 0 x_c, y_c = 5, 5 x_pos = x_c + shift_x y_pos = y_c + shift_y #kernel_shifted = interp.shift(kernel, [shift_y, shift_x], order=1) image = image_util.add_layer2image(image, x_pos, y_pos, kernel, order=1) print(image) kernel_new = kernel_util.cutout_source(x_pos=x_pos, y_pos=y_pos, image=image, kernelsize=kernel_size) npt.assert_almost_equal(kernel_new[2, 2], kernel[2, 2], decimal=2)
def point_like_source_cutouts(x_pos, y_pos, image_list, cutout_size): """ cutouts of point-like objects :param x_pos: list of image positions in pixel units :param y_pos: list of image position in pixel units :param image_list: list of 2d numpy arrays with cleaned images, with all contaminating sources removed except the point-like object to be cut out. :param cutout_size: odd integer, size of cutout. :return: list of cutouts """ star_cutout_list = [] for l in range(len(x_pos)): x_int = int(round(x_pos[l])) y_int = int(round(y_pos[l])) star_cutout = kernel_util.cutout_source(x_int, y_int, image_list[l], cutout_size, shift=False) star_cutout_list.append(star_cutout) return star_cutout_list
def error_map_estimate_new(self, psf_kernel, psf_kernel_list, ra_image, dec_image, point_amp, supersampling_factor, error_map_radius=None): """ relative uncertainty in the psf model (in quadrature) per pixel based on residuals achieved in the image :param psf_kernel: PSF kernel (super-sampled) :param psf_kernel_list: list of individual best PSF kernel estimates :param ra_image: image positions in angles :param dec_image: image positions in angles :param point_amp: image amplitude :param supersampling_factor: super-sampling factor :param error_map_radius: radius (in angle) to cut the error map :return: psf error map such that square of the uncertainty gets boosted by error_map * (psf * amp)**2 """ kernel_low = kernel_util.degrade_kernel(psf_kernel, supersampling_factor) error_map_list = np.zeros( (len(psf_kernel_list), len(kernel_low), len(kernel_low))) x_pos, y_pos = self._image_model_class.Data.map_coord2pix( ra_image, dec_image) for i, psf_kernel_i in enumerate(psf_kernel_list): kernel_low_i = kernel_util.degrade_kernel(psf_kernel_i, supersampling_factor) x, y, amp_i = x_pos[i], y_pos[i], point_amp[i] x_int = int(round(x)) y_int = int(round(y)) C_D_cutout = kernel_util.cutout_source( x_int, y_int, self._image_model_class.Data.C_D, len(kernel_low_i), shift=False) residuals_i = np.abs(kernel_low - kernel_low_i) residuals_i -= np.sqrt(C_D_cutout) / amp_i residuals_i[residuals_i < 0] = 0 error_map_list[i, :, :] = residuals_i**2 error_map = np.median(error_map_list, axis=0) error_map[kernel_low > 0] /= kernel_low[kernel_low > 0]**2 error_map = np.nan_to_num(error_map) error_map[error_map > 1] = 1 # cap on error to be the same # mask the error map outside a certain radius (can avoid double counting of errors when map is overlapping if error_map_radius is not None: pixel_scale = self._image_model_class.Data.pixel_width x_grid, y_grid = util.make_grid(numPix=len(error_map), deltapix=pixel_scale) mask = mask_util.mask_azimuthal(x_grid, y_grid, center_x=0, center_y=0, r=error_map_radius) error_map *= util.array2image(mask) return error_map
def psf_estimate_individual(self, ra_image, dec_image, point_amp, residuals, cutout_size, kernel_guess, supersampling_factor, block_center_neighbour): """ :param ra_image: list; position in angular units of the image :param dec_image: list; position in angular units of the image :param point_amp: list of model amplitudes of point sources :param residuals: data - model :param cutout_size: pixel size of cutout around single star/quasar to be considered for the psf reconstruction :param kernel_guess: initial guess of super-sampled PSF :param supersampling_factor: int, super-sampling factor :param block_center_neighbour: :return: list of best-guess PSF's for each star based on the residual patterns """ mask = self._image_model_class.likelihood_mask ra_grid, dec_grid = self._image_model_class.Data.pixel_coordinates ra_grid = util.image2array(ra_grid) dec_grid = util.image2array(dec_grid) radius = block_center_neighbour x_, y_ = self._image_model_class.Data.map_coord2pix( ra_image, dec_image) kernel_list = [] for l in range(len(ra_image)): mask_point_source = self.mask_point_source(ra_image, dec_image, ra_grid, dec_grid, radius, i=l) mask_i = mask * mask_point_source # cutout residuals x_int = int(round(x_[l])) y_int = int(round(y_[l])) residual_cutout = kernel_util.cutout_source(x_int, y_int, residuals, cutout_size + 2, shift=False) # cutout the mask mask_cutout = kernel_util.cutout_source(x_int, y_int, mask_i, cutout_size + 2, shift=False) # apply mask residual_cutout_mask = residual_cutout * mask_cutout # re-scale residuals with point source brightness residual_cutout_mask /= point_amp[l] # enlarge residuals by super-sampling factor residual_cutout_mask = residual_cutout_mask.repeat( supersampling_factor, axis=0).repeat(supersampling_factor, axis=1) # inverse shift residuals shift_x = (x_int - x_[l]) * supersampling_factor shift_y = (y_int - y_[l]) * supersampling_factor # for odd number super-sampling if supersampling_factor % 2 == 1: residuals_shifted = ndimage.shift(residual_cutout_mask, shift=[shift_y, shift_x], order=1) else: # for even number super-sampling half a super-sampled pixel offset needs to be performed residuals_shifted = ndimage.shift( residual_cutout_mask, shift=[shift_y - 0.5, shift_x - 0.5], order=1) # and the last column and row need to be removed residuals_shifted = residuals_shifted[:-1, :-1] # re-size shift residuals psf_size = len(kernel_guess) residuals_shifted = image_util.cut_edges(residuals_shifted, psf_size) # normalize residuals correction = residuals_shifted - np.mean(residuals_shifted) # correct old PSF with inverse shifted residuals kernel_new = kernel_guess + correction kernel_list.append(kernel_new) return kernel_list
def cutout_psf(self, x, y, image_list, kernelsize, mask, mask_point_source_list, kernel_init, symmetry=1): """ :param x_: :param y_: :param image_list: list of images (i.e. data - all models subtracted, except a single point source) :param kernelsize: :return: """ n = len(x) * symmetry angle = 360. / symmetry kernel_list = np.zeros((n, kernelsize, kernelsize)) i = 0 for l in range(len(x)): # cutout the star x_, y_ = x[l], y[l] x_int = int(round(x_)) y_int = int(round(y_)) star_cutout = kernel_util.cutout_source(x_int, y_int, image_list[l], kernelsize + 2, shift=False) # cutout the mask mask_i = mask * mask_point_source_list[l] mask_cutout = kernel_util.cutout_source(x_int, y_int, mask_i, kernelsize + 2, shift=False) # enlarge the initial PSF kernel to the new cutout size kernel_enlarged = np.zeros((kernelsize+2, kernelsize+2)) kernel_enlarged[1:-1, 1:-1] = kernel_init # shift the initial kernel to the shift of the star shift_x = x_int - x_ shift_y = y_int - y_ kernel_shifted = interp.shift(kernel_enlarged, [-shift_y, -shift_x], order=1) # compute normalization of masked and unmasked region of the shifted kernel # norm_masked = np.sum(kernel_shifted[mask_i == 0]) norm_unmaksed = np.sum(kernel_shifted[mask_cutout == 1]) # normalize star within the unmasked region to the norm of the initial kernel of the same region star_cutout /= np.sum(star_cutout[mask_cutout == 1]) * norm_unmaksed # replace mask with shifted initial kernel (+2 size) star_cutout[mask_cutout == 0] = kernel_shifted[mask_cutout == 0] star_cutout[star_cutout < 0] = 0 # de-shift kernel kernel_deshifted = kernel_util.de_shift_kernel(star_cutout, shift_x, shift_y) # re-size kernel kernel_deshifted = image_util.cut_edges(kernel_deshifted, kernelsize) # re-normalize kernel again kernel_deshifted = kernel_util.kernel_norm(kernel_deshifted) """ kernel_shifted = kernel_util.cutout_source(x_[l], y_[l], image_list[l], kernelsize + 2) # don't de-shift it here mask_i = mask * mask_point_source_list[l] mask_cutout = kernel_util.cutout_source(int(round(x_[l])), int(round(x_[l])), mask_i, kernelsize + 2, shift=False) kernel_shifted[kernel_shifted < 0] = 0 kernel_shifted *= mask_cutout kernel_init = kernel_util.kernel_norm(kernel_init) mask_cutout = image_util.cut_edges(mask_cutout, kernelsize) kernel_shifted = image_util.cut_edges(kernel_shifted, kernelsize) kernel_norm = np.sum(kernel_init[mask_cutout == 1]) kernel_shifted = kernel_util.kernel_norm(kernel_shifted) kernel_shifted *= kernel_norm kernel_shifted[mask_cutout == 0] = kernel_init[mask_cutout == 0] #kernel_shifted[mask_cutout == 1] /= (np.sum(kernel_init[mask_cutout == 1]) * np.sum(kernel_shifted[mask_cutout == 1])) """ for k in range(symmetry): kernel_rotated = image_util.rotateImage(kernel_deshifted, angle * k) kernel_norm = kernel_util.kernel_norm(kernel_rotated) try: kernel_list[i, :, :] = kernel_norm except: raise ValueError("cutout kernel has not the same shape as the PSF." " This is probably because the cutout of the psf hits the boarder of the data." "Use a smaller PSF or a larger data frame for the modelling.") i += 1 return kernel_list