Source code for xrtpy.image_correction.deconvolve

"""
Functionality for deconvolving XRT image data with the point spread function.
"""

from datetime import datetime
from urllib.parse import urljoin

import numpy as np
from numpy.fft import fft2, fftshift, ifft2
from sunpy.data import manager
from sunpy.image.resample import resample
from sunpy.image.transform import affine_transform
from sunpy.map import Map

from xrtpy.util import SSW_MIRRORS

__all__ = ["deconvolve"]


[docs] @manager.require( "PSF560.fits", [ urljoin(mirror, "hinode/xrt/idl/util/XRT20170324_151721.0.PSF560.fits") for mirror in SSW_MIRRORS ], "0eaa5da6fb69661e7f46d1f0c463e4b3b1745426a399a4fbc53fc0c0ae87dd0d", ) @manager.require( "PSF1000.fits", [ urljoin(mirror, "hinode/xrt/idl/util/XRT20170324_161721.0.PSF1000.fits") for mirror in SSW_MIRRORS ], "95590a7174692977a2f111b932811c9c7ae105a59b93bfe6c96fba862cefacf1", ) def deconvolve(image_map, niter=5, verbose=False, psf1keV=False): """ Use the XRT mirror model point spread function (PSF) to deconvolve an XRT image Parameters: ----------- image_map : ~sunpy.map.sources.hinode.XRTMap |Map| for the input XRT image niter : integer, optional (default = 5) number of iterations to perform verbose : boolean, optional if True, extra information is printed psf1keV : boolean, optional if True, use the 1.0 keV PSF instead of the default 560 eV PSF. Returns: -------- deconv_map : ~sunpy.map.sources.hinode.XRTMap |Map| for the output deconvolved image """ if psf1keV: used_psf = manager.get("PSF1000.fits") else: used_psf = manager.get("PSF560.fits") psf_map = Map(used_psf) psf_meta = psf_map.meta if verbose: print(f"DECONVOLVE: Using PSF in\n{used_psf}") image_meta = image_map.meta data_chip_sum = image_meta["chip_sum"] # place PSF & data on same chip sum. if psf_meta["chip_sum"] != data_chip_sum: if verbose: print( "DECONVOLVE: Input data and PSF have different" " chip sums. Binning PSF..." ) psf_map = _rebin_psf(psf_map, image_map.meta) data = np.clip(image_map.data, 0.0, None) deconvolve_hist = ( f"(DECONVOLVE) Deconvolved with {used_psf}, bin={data_chip_sum}, niter={niter}." ) # if input image size is smaller than the psf (2048x2048), put the # image in the center of zero array the same size as the psf extract_data = False naxis1 = image_map.meta["naxis1"] naxis2 = image_map.meta["naxis2"] psf_naxis1 = psf_map.meta["naxis1"] psf_naxis2 = psf_map.meta["naxis2"] if (naxis1 * data_chip_sum != psf_naxis1 * psf_meta["chip_sum"]) or ( naxis2 * data_chip_sum != psf_naxis1 * psf_meta["chip_sum"] ): extract_data = True if verbose: print("Input data not same size as PSF. Dropping image in zero array.") tmp_data = np.zeros((psf_naxis1, psf_naxis2)) ddx = naxis1 // 2 ddy = naxis2 // 2 xcen = psf_naxis1 // 2 - 1 ycen = psf_naxis2 // 2 - 1 tmp_data[xcen - ddx : xcen + ddx, ycen - ddy : ycen + ddy] = data else: tmp_data = data tmp_deconv = _richardson_lucy_deconvolution(tmp_data, psf_map.data, num_iter=5) if extract_data: tmp_deconv = tmp_deconv[xcen - ddx : xcen + ddx, ycen - ddy : ycen + ddy] deconv_data = np.minimum(tmp_deconv, 2500.0) date = datetime.now().ctime() added_hist = f"{__name__}: ({date}) " + deconvolve_hist deconv_meta = image_meta deconv_meta["history"] = image_meta["history"] + added_hist return Map(deconv_data, deconv_meta)
def _fft_2dim_convolution(image1, image2, correlation=False): """ Convolve (or optionally correlate) two images """ if correlation: fftres = ifft2(fft2(image1) * np.conj(fft2(image2))) else: fftres = ifft2(fft2(image1) * fft2(image2)) return fftshift(fftres) def _richardson_lucy_deconvolution(image, psf, num_iter=5): """ Use the Richardson-Lucy algorithm to deconvolve an image. """ psfnorm = _fft_2dim_convolution(psf, np.ones_like(psf)) ohat = np.cdouble(image) for _ in range(num_iter): ihat = _fft_2dim_convolution(psf, ohat) ohat *= _fft_2dim_convolution(image / ihat, psf, correlation=True) / psfnorm return np.abs(ohat) def _rebin_psf(psf_map, image_meta): """ Rebin the point spread function (psf) to match the dimensions of an image. It's assumed that the image is smaller than the psf array Parameters: ----------- psf_map : ~sunpy.map.sources.hinode.XRTMap |Map| for the point spread function image_meta : ~sunpy.util.metadata.MetaDict meta data for an image Returns: -------- downsampled map : ~sunpy.map.sources.hinode.XRTMap |Map| for the psf that has been binned to match the pixel size of the data """ psf_image = psf_map.data psf_meta = psf_map.meta center = (np.array(psf_image.shape)[::-1] - 1) / 2.0 new_center = (center[0] - 0.5, center[1] - 0.5) rmatrix = np.array([[1.0, 0.0], [0.0, 1.0]]) psf_shifted = affine_transform( psf_image, rmatrix, recenter=True, image_center=new_center, order=3, method="scikit-image", missing=0.0, ) psf_shifted = psf_shifted / psf_shifted.sum() dims = ( psf_meta["naxis1"] // image_meta["chip_sum"], psf_meta["naxis2"] // image_meta["chip_sum"], ) downsampled = resample(psf_shifted, dims, method="spline", center=True) psf = downsampled / downsampled.sum() psf_meta["chip_sum"] = image_meta["chip_sum"] psf_meta["naxis1"] = psf.shape[0] psf_meta["naxis2"] = psf.shape[1] return Map(psf, psf_meta)