1
|
|
|
import numpy as np |
|
|
|
|
2
|
|
|
import collections |
|
|
|
|
3
|
|
|
from hawc_hal import flat_sky_projection |
4
|
|
|
from hawc_hal.sphere_dist import sphere_dist |
5
|
|
|
import reproject |
|
|
|
|
6
|
|
|
|
7
|
|
|
|
8
|
|
|
def _divide_in_blocks(arr, nrows, ncols): |
9
|
|
|
""" |
10
|
|
|
Return an array of shape (n, nrows, ncols) where |
11
|
|
|
n * nrows * ncols = arr.size |
12
|
|
|
|
13
|
|
|
If arr is a 2D array, the returned array should look like n subblocks with |
14
|
|
|
each subblock preserving the "physical" layout of arr. |
15
|
|
|
""" |
16
|
|
|
h, w = arr.shape |
|
|
|
|
17
|
|
|
return (arr.reshape(h // nrows, nrows, -1, ncols) |
18
|
|
|
.swapaxes(1, 2) |
19
|
|
|
.reshape(-1, nrows, ncols)) |
20
|
|
|
|
21
|
|
|
|
22
|
|
|
class PSFInterpolator(object): |
|
|
|
|
23
|
|
|
|
24
|
|
|
def __init__(self, psf_wrapper, flat_sky_proj): |
25
|
|
|
|
26
|
|
|
self._psf = psf_wrapper |
27
|
|
|
|
28
|
|
|
self._flat_sky_p = flat_sky_proj |
29
|
|
|
|
30
|
|
|
# This will contain cached source images |
31
|
|
|
self._point_source_images = collections.OrderedDict() |
32
|
|
|
|
33
|
|
|
def _get_point_source_image_aitoff(self, ra, dec, psf_integration_method): |
|
|
|
|
34
|
|
|
|
35
|
|
|
# Get the density for the required (RA, Dec) points by interpolating the density profile |
36
|
|
|
|
37
|
|
|
# First we obtain an image with a flat sky projection centered exactly on the point source |
38
|
|
|
ancillary_image_pixel_size = 0.05 |
39
|
|
|
pixel_side = 2 * int(np.ceil(self._psf.truncation_radius / ancillary_image_pixel_size)) |
40
|
|
|
ancillary_flat_sky_proj = flat_sky_projection.FlatSkyProjection(ra, dec, ancillary_image_pixel_size, |
41
|
|
|
pixel_side, pixel_side) |
42
|
|
|
|
43
|
|
|
# Now we compute the angular distance of all pixels in the ancillary image with respect to the center |
44
|
|
|
angular_distances = sphere_dist(ra, dec, ancillary_flat_sky_proj.ras, ancillary_flat_sky_proj.decs) |
45
|
|
|
|
46
|
|
|
# Compute the brightness (i.e., the differential PSF) |
47
|
|
|
ancillary_brightness = self._psf.brightness(angular_distances).reshape((pixel_side, pixel_side)) * \ |
48
|
|
|
self._flat_sky_p.project_plane_pixel_area |
49
|
|
|
|
50
|
|
|
# Now reproject this brightness on the new image |
51
|
|
|
if psf_integration_method == 'exact': |
52
|
|
|
|
53
|
|
|
reprojection_method = reproject.reproject_exact |
54
|
|
|
additional_keywords = {'parallel': False} |
55
|
|
|
|
56
|
|
|
else: |
57
|
|
|
|
58
|
|
|
reprojection_method = reproject.reproject_interp |
59
|
|
|
additional_keywords = {} |
60
|
|
|
|
61
|
|
|
brightness, _ = reprojection_method((ancillary_brightness, ancillary_flat_sky_proj.wcs), |
62
|
|
|
self._flat_sky_p.wcs, shape_out=(self._flat_sky_p.npix_height, |
63
|
|
|
self._flat_sky_p.npix_width), |
64
|
|
|
**additional_keywords) |
65
|
|
|
|
66
|
|
|
brightness[np.isnan(brightness)] = 0.0 |
67
|
|
|
|
68
|
|
|
# Now "integrate", i.e., multiply by pixel area |
69
|
|
|
point_source_img_ait = brightness |
70
|
|
|
|
71
|
|
|
# # First let's compute the core of the PSF, i.e., the central area with a radius of 0.5 deg, |
72
|
|
|
# # using a small pixel size |
73
|
|
|
# |
74
|
|
|
# # We use the oversampled version of the flat sky projection to make sure we compute an integral |
75
|
|
|
# # with the right pixelization |
76
|
|
|
# |
77
|
|
|
# oversampled = self._flat_sky_p.oversampled |
78
|
|
|
# oversample_factor = self._flat_sky_p.oversample_factor |
79
|
|
|
# |
80
|
|
|
# # Find bounding box for a faster selection |
81
|
|
|
# |
82
|
|
|
# angular_distances_core, core_idx = oversampled.get_spherical_distances_from(ra, dec, cutout_radius=5.0) |
83
|
|
|
# |
84
|
|
|
# core_densities = np.zeros(oversampled.ras.shape) |
85
|
|
|
# |
86
|
|
|
# core_densities[core_idx] = self._psf.brightness(angular_distances_core) * oversampled.project_plane_pixel_area |
87
|
|
|
# |
88
|
|
|
# # Now downsample by summing every "oversample_factor" pixels |
89
|
|
|
# |
90
|
|
|
# blocks = _divide_in_blocks(core_densities.reshape((oversampled.npix_height, oversampled.npix_width)), |
91
|
|
|
# oversample_factor, |
92
|
|
|
# oversample_factor) |
93
|
|
|
# |
94
|
|
|
# densities = blocks.flatten().reshape([-1, oversample_factor**2]).sum(axis=1) # type: np.ndarray |
95
|
|
|
# |
96
|
|
|
# assert densities.shape[0] == (self._flat_sky_p.npix_height * self._flat_sky_p.npix_width) |
97
|
|
|
# |
98
|
|
|
# # Now that we have a "densities" array with the right (not oversampled) resolution, |
99
|
|
|
# # let's update the elements outside the core, which are still zero |
100
|
|
|
# angular_distances_, within_outer_radius = self._flat_sky_p.get_spherical_distances_from(ra, dec, |
101
|
|
|
# cutout_radius=10.0) |
102
|
|
|
# |
103
|
|
|
# # Make a vector with the same shape of "densities" (effectively a padding of what we just computed) |
104
|
|
|
# angular_distances = np.zeros_like(densities) |
105
|
|
|
# angular_distances[within_outer_radius] = angular_distances_ |
106
|
|
|
# |
107
|
|
|
# to_be_updated = (densities == 0) |
108
|
|
|
# idx = (within_outer_radius & to_be_updated) |
109
|
|
|
# densities[idx] = self._psf.brightness(angular_distances[idx]) * self._flat_sky_p.project_plane_pixel_area |
110
|
|
|
# |
111
|
|
|
# # NOTE: we check that the point source image is properly normalized in the convolved point source class. |
112
|
|
|
# # There we know which source we are talking about and for which bin, so we can print a more helpful |
113
|
|
|
# # help message |
114
|
|
|
# |
115
|
|
|
# # Reshape to required shape |
116
|
|
|
# point_source_img_ait = densities.reshape((self._flat_sky_p.npix_height, self._flat_sky_p.npix_width)).T |
117
|
|
|
|
118
|
|
|
return point_source_img_ait |
119
|
|
|
|
120
|
|
|
def point_source_image(self, ra_src, dec_src, psf_integration_method='exact'): |
|
|
|
|
121
|
|
|
|
122
|
|
|
# Make a unique key for this request |
123
|
|
|
key = (ra_src, dec_src, psf_integration_method) |
124
|
|
|
|
125
|
|
|
# If we already computed this image, return it, otherwise compute it from scratch |
126
|
|
|
if key in self._point_source_images: |
127
|
|
|
|
128
|
|
|
point_source_img_ait = self._point_source_images[key] |
129
|
|
|
|
130
|
|
|
else: |
131
|
|
|
|
132
|
|
|
point_source_img_ait = self._get_point_source_image_aitoff(ra_src, dec_src, psf_integration_method) |
133
|
|
|
|
134
|
|
|
# Store for future use |
135
|
|
|
|
136
|
|
|
self._point_source_images[key] = point_source_img_ait |
137
|
|
|
|
138
|
|
|
# Limit the size of the cache. If we have exceeded 20 images, we drop the oldest 10 |
139
|
|
|
if len(self._point_source_images) > 20: |
140
|
|
|
|
141
|
|
|
while len(self._point_source_images) > 10: |
142
|
|
|
# FIFO removal (the oldest calls are removed first) |
143
|
|
|
self._point_source_images.popitem(last=False) |
144
|
|
|
|
145
|
|
|
return point_source_img_ait |
146
|
|
|
|
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.