1
|
|
|
import numpy as np |
|
|
|
|
2
|
|
|
from numpy.fft import rfftn, irfftn |
3
|
|
|
#from scipy.signal import fftconvolve |
4
|
|
|
from scipy.fftpack import helper |
5
|
|
|
|
6
|
|
|
|
7
|
|
|
from psf_interpolator import PSFInterpolator |
|
|
|
|
8
|
|
|
from psf_wrapper import PSFWrapper |
|
|
|
|
9
|
|
|
|
10
|
|
|
|
11
|
|
|
class PSFConvolutor(object): |
|
|
|
|
12
|
|
|
|
13
|
|
|
def __init__(self, psf_wrapper, flat_sky_proj): |
14
|
|
|
|
15
|
|
|
self._psf = psf_wrapper # type: PSFWrapper |
16
|
|
|
self._flat_sky_proj = flat_sky_proj |
17
|
|
|
|
18
|
|
|
# Compute an image of the PSF on the current defined flat sky projection |
19
|
|
|
interpolator = PSFInterpolator(psf_wrapper, flat_sky_proj) |
20
|
|
|
psf_stamp = interpolator.point_source_image(flat_sky_proj.ra_center, flat_sky_proj.dec_center) |
21
|
|
|
|
22
|
|
|
# Crop the kernel at the appropriate radius for this PSF (making sure is divisible by 2) |
23
|
|
|
kernel_radius_px = int(np.ceil(self._psf.kernel_radius / flat_sky_proj.pixel_size)) |
24
|
|
|
pixels_to_keep = kernel_radius_px * 2 |
25
|
|
|
|
26
|
|
|
assert pixels_to_keep <= psf_stamp.shape[0] and \ |
27
|
|
|
pixels_to_keep <= psf_stamp.shape[1], \ |
28
|
|
|
"The kernel is too large with respect to the model image. Enlarge your model radius." |
29
|
|
|
|
30
|
|
|
xoff = (psf_stamp.shape[0] - pixels_to_keep) // 2 |
31
|
|
|
yoff = (psf_stamp.shape[1] - pixels_to_keep) // 2 |
32
|
|
|
|
33
|
|
|
self._kernel = psf_stamp[yoff:-yoff, xoff:-xoff] |
34
|
|
|
|
35
|
|
|
assert np.isclose(self._kernel.sum(), 1.0, rtol=1e-2), "Failed to generate proper kernel normalization: got _kernel.sum() = %f; expected 1.0+-0.01." % self._kernel.sum() |
|
|
|
|
36
|
|
|
|
37
|
|
|
# Renormalize to exactly 1 |
38
|
|
|
self._kernel = self._kernel / self._kernel.sum() |
39
|
|
|
|
40
|
|
|
self._expected_shape = (flat_sky_proj.npix_height, flat_sky_proj.npix_width) |
41
|
|
|
|
42
|
|
|
s1 = np.array(self._expected_shape) |
|
|
|
|
43
|
|
|
s2 = np.array(self._kernel.shape) |
|
|
|
|
44
|
|
|
|
45
|
|
|
shape = s1 + s2 - 1 |
46
|
|
|
|
47
|
|
|
self._fshape = [helper.next_fast_len(int(d)) for d in shape] |
48
|
|
|
self._fslice = tuple([slice(0, int(sz)) for sz in shape]) |
49
|
|
|
|
50
|
|
|
self._psf_fft = rfftn(self._kernel, self._fshape) |
51
|
|
|
|
52
|
|
|
@property |
53
|
|
|
def kernel(self): |
|
|
|
|
54
|
|
|
return self._kernel |
55
|
|
|
|
56
|
|
|
# def extended_source_image(self, ideal_image): |
57
|
|
|
# |
58
|
|
|
# conv = fftconvolve(ideal_image, self._kernel, mode='same') |
59
|
|
|
# |
60
|
|
|
# return conv |
61
|
|
|
|
62
|
|
|
def extended_source_image(self, ideal_image): |
|
|
|
|
63
|
|
|
|
64
|
|
|
# Convolve |
65
|
|
|
|
66
|
|
|
assert np.alltrue(ideal_image.shape == self._expected_shape), "Shape of image to be convolved is not correct." |
67
|
|
|
|
68
|
|
|
ret = irfftn(rfftn(ideal_image, self._fshape) * self._psf_fft, self._fshape)[self._fslice].copy() |
69
|
|
|
|
70
|
|
|
conv = _centered(ret, self._expected_shape) |
71
|
|
|
|
72
|
|
|
return conv |
73
|
|
|
|
74
|
|
|
|
75
|
|
|
def _centered(arr, newsize): |
76
|
|
|
# Return the center newsize portion of the array. |
77
|
|
|
newsize = np.asarray(newsize) |
78
|
|
|
currsize = np.array(arr.shape) |
79
|
|
|
startind = (currsize - newsize) // 2 |
80
|
|
|
endind = startind + newsize |
81
|
|
|
myslice = [slice(startind[k], endind[k]) for k in range(len(endind))] |
82
|
|
|
return arr[tuple(myslice)] |
83
|
|
|
|
The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:
If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.