drizzle 2.2.0__cp313-cp313-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- drizzle/__init__.py +13 -0
- drizzle/cdrizzle.cp313-win_amd64.pyd +0 -0
- drizzle/resample.py +1158 -0
- drizzle/tests/__init__.py +0 -0
- drizzle/tests/helpers.py +215 -0
- drizzle/tests/test_cdrizzle.py +29 -0
- drizzle/tests/test_overlap_calc.py +287 -0
- drizzle/tests/test_resample.py +2202 -0
- drizzle/tests/test_utils.py +245 -0
- drizzle/util.py +35 -0
- drizzle/utils.py +278 -0
- drizzle-2.2.0.dist-info/METADATA +172 -0
- drizzle-2.2.0.dist-info/RECORD +16 -0
- drizzle-2.2.0.dist-info/WHEEL +5 -0
- drizzle-2.2.0.dist-info/licenses/LICENSE.rst +31 -0
- drizzle-2.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,2202 @@
|
|
|
1
|
+
import math
|
|
2
|
+
import os
|
|
3
|
+
from itertools import product
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from astropy import wcs
|
|
9
|
+
from astropy.convolution import Gaussian2DKernel
|
|
10
|
+
from drizzle import cdrizzle, resample, utils
|
|
11
|
+
|
|
12
|
+
from .helpers import wcs_from_file
|
|
13
|
+
|
|
14
|
+
TEST_DIR = os.path.abspath(os.path.dirname(__file__))
|
|
15
|
+
DATA_DIR = os.path.join(TEST_DIR, "data")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def bound_image(image):
|
|
19
|
+
"""
|
|
20
|
+
Compute region where image is non-zero
|
|
21
|
+
"""
|
|
22
|
+
coords = np.nonzero(image)
|
|
23
|
+
ymin = coords[0].min()
|
|
24
|
+
ymax = coords[0].max()
|
|
25
|
+
xmin = coords[1].min()
|
|
26
|
+
xmax = coords[1].max()
|
|
27
|
+
return (ymin, ymax, xmin, xmax)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def centroid(image, size, center):
|
|
31
|
+
"""
|
|
32
|
+
Compute the centroid of a rectangular area
|
|
33
|
+
"""
|
|
34
|
+
ylo = int(center[0] - size / 2)
|
|
35
|
+
yhi = min(ylo + size, image.shape[0])
|
|
36
|
+
xlo = int(center[1] - size / 2)
|
|
37
|
+
xhi = min(xlo + size, image.shape[1])
|
|
38
|
+
|
|
39
|
+
yx1 = np.mgrid[ylo:yhi, xlo:xhi, 1:2]
|
|
40
|
+
center = (yx1[..., 0] * image[ylo:yhi, xlo:xhi]).sum(
|
|
41
|
+
axis=(1, 2),
|
|
42
|
+
dtype=np.float64,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
if center[2] == 0.0:
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
center[0] /= center[2]
|
|
49
|
+
center[1] /= center[2]
|
|
50
|
+
return center
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def centroid_close(list_of_centroids, size, point):
|
|
54
|
+
"""
|
|
55
|
+
Find if any centroid is close to a point
|
|
56
|
+
"""
|
|
57
|
+
for i in range(len(list_of_centroids) - 1, -1, -1):
|
|
58
|
+
if abs(list_of_centroids[i][0] - point[0]) < int(size / 2) and abs(
|
|
59
|
+
list_of_centroids[i][1] - point[1]
|
|
60
|
+
) < int(size / 2):
|
|
61
|
+
return 1
|
|
62
|
+
|
|
63
|
+
return 0
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def centroid_compare(centroid):
|
|
67
|
+
return centroid[1]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def centroid_distances(image1, image2, amp, size):
|
|
71
|
+
"""
|
|
72
|
+
Compute a list of centroids and the distances between them in two images
|
|
73
|
+
"""
|
|
74
|
+
distances = []
|
|
75
|
+
list_of_centroids = centroid_list(image2, amp, size)
|
|
76
|
+
for cc in list_of_centroids:
|
|
77
|
+
center1 = centroid(image1, size, cc)
|
|
78
|
+
center2 = centroid(image2, size, cc)
|
|
79
|
+
if center1 is None:
|
|
80
|
+
continue
|
|
81
|
+
|
|
82
|
+
disty = center2[0] - center1[0]
|
|
83
|
+
distx = center2[1] - center1[1]
|
|
84
|
+
dist = math.sqrt(disty * disty + distx * distx)
|
|
85
|
+
dflux = abs(center2[2] - center1[2])
|
|
86
|
+
distances.append([dist, dflux, center1, center2])
|
|
87
|
+
|
|
88
|
+
distances.sort(key=centroid_compare)
|
|
89
|
+
return distances
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def centroid_list(image, amp, size):
|
|
93
|
+
"""
|
|
94
|
+
Find the next centroid
|
|
95
|
+
"""
|
|
96
|
+
list_of_centroids = []
|
|
97
|
+
points = np.transpose(np.nonzero(image > amp))
|
|
98
|
+
for point in points:
|
|
99
|
+
if not centroid_close(list_of_centroids, size, point):
|
|
100
|
+
center = centroid(image, size, point)
|
|
101
|
+
list_of_centroids.append(center)
|
|
102
|
+
|
|
103
|
+
return list_of_centroids
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def centroid_statistics(title, fname, image1, image2, amp, size):
|
|
107
|
+
"""
|
|
108
|
+
write centroid statistics to compare differences btw two images
|
|
109
|
+
"""
|
|
110
|
+
stats = ("minimum", "median", "maximum")
|
|
111
|
+
images = (None, None, image1, image2)
|
|
112
|
+
im_type = ("", "", "test", "reference")
|
|
113
|
+
|
|
114
|
+
diff = []
|
|
115
|
+
distances = centroid_distances(image1, image2, amp, size)
|
|
116
|
+
indexes = (0, int(len(distances) / 2), len(distances) - 1)
|
|
117
|
+
fd = open(fname, "w")
|
|
118
|
+
fd.write(f"*** {title:s} ***\n")
|
|
119
|
+
|
|
120
|
+
if len(distances) == 0:
|
|
121
|
+
diff = [0.0, 0.0, 0.0]
|
|
122
|
+
fd.write("No matches!!\n")
|
|
123
|
+
|
|
124
|
+
elif len(distances) == 1:
|
|
125
|
+
diff = [distances[0][0], distances[0][0], distances[0][0]]
|
|
126
|
+
|
|
127
|
+
fd.write("1 match\n")
|
|
128
|
+
fd.write(f"distance = {distances[0][0]:f} flux difference = {distances[0][1]:f}\n")
|
|
129
|
+
|
|
130
|
+
for j in range(2, 4):
|
|
131
|
+
ylo = int(distances[0][j][0]) - 1
|
|
132
|
+
yhi = int(distances[0][j][0]) + 2
|
|
133
|
+
xlo = int(distances[0][j][1]) - 1
|
|
134
|
+
xhi = int(distances[0][j][1]) + 2
|
|
135
|
+
subimage = images[j][ylo:yhi, xlo:xhi]
|
|
136
|
+
fd.write(
|
|
137
|
+
f"\n{im_type[j]} image centroid = "
|
|
138
|
+
f"({distances[0][j][0]:f}, {distances[0][j][1]:f}) "
|
|
139
|
+
f"image flux = {distances[0][j][2]:f}\n"
|
|
140
|
+
)
|
|
141
|
+
fd.write(str(subimage) + "\n")
|
|
142
|
+
|
|
143
|
+
else:
|
|
144
|
+
fd.write(f"{len(distances)} matches\n")
|
|
145
|
+
|
|
146
|
+
for k in range(3):
|
|
147
|
+
i = indexes[k]
|
|
148
|
+
diff.append(distances[i][0])
|
|
149
|
+
fd.write(
|
|
150
|
+
f"\n{stats[k]} distance = {distances[i][0]:f} "
|
|
151
|
+
f"flux difference = {distances[i][1]:f}\n"
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
for j in range(2, 4):
|
|
155
|
+
ylo = int(distances[i][j][0]) - 1
|
|
156
|
+
yhi = int(distances[i][j][0]) + 2
|
|
157
|
+
xlo = int(distances[i][j][1]) - 1
|
|
158
|
+
xhi = int(distances[i][j][1]) + 2
|
|
159
|
+
subimage = images[j][ylo:yhi, xlo:xhi]
|
|
160
|
+
fd.write(
|
|
161
|
+
f"\n{stats[k]} {im_type[j]} image centroid = "
|
|
162
|
+
f"({distances[i][j][0]:f}, {distances[i][j][1]:f}) "
|
|
163
|
+
f"image flux = {distances[i][j][2]:f}\n"
|
|
164
|
+
)
|
|
165
|
+
fd.write(str(subimage) + "\n")
|
|
166
|
+
|
|
167
|
+
fd.close()
|
|
168
|
+
return tuple(diff)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def make_point_image(shape, point, value):
|
|
172
|
+
"""
|
|
173
|
+
Create an image with a single point set
|
|
174
|
+
"""
|
|
175
|
+
output_image = np.zeros(shape, dtype=np.float32)
|
|
176
|
+
output_image[point] = value
|
|
177
|
+
return output_image
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def make_grid_image(shape, spacing, value):
|
|
181
|
+
"""
|
|
182
|
+
Create an image with points on a grid set
|
|
183
|
+
"""
|
|
184
|
+
output_image = np.zeros(shape, dtype=np.float32)
|
|
185
|
+
|
|
186
|
+
shape = output_image.shape
|
|
187
|
+
half_space = int(spacing / 2)
|
|
188
|
+
for y in range(half_space, shape[0], spacing):
|
|
189
|
+
for x in range(half_space, shape[1], spacing):
|
|
190
|
+
output_image[y, x] = value
|
|
191
|
+
|
|
192
|
+
return output_image
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@pytest.fixture(scope="module")
|
|
196
|
+
def nrcb5_stars():
|
|
197
|
+
full_file_name = os.path.join(DATA_DIR, "nrcb5_sip_wcs.hdr")
|
|
198
|
+
path = os.path.join(DATA_DIR, full_file_name)
|
|
199
|
+
|
|
200
|
+
wcs, data = wcs_from_file(path, return_data=True)
|
|
201
|
+
dq = np.zeros(data.shape, dtype=np.int32)
|
|
202
|
+
wht = np.zeros(data.shape, dtype=np.float32)
|
|
203
|
+
var = np.zeros(data.shape, dtype=np.float32)
|
|
204
|
+
|
|
205
|
+
np.random.seed(0)
|
|
206
|
+
|
|
207
|
+
patch_size = 21
|
|
208
|
+
patch_area = patch_size**2
|
|
209
|
+
p2 = patch_size // 2
|
|
210
|
+
# add border so that resampled partial pixels can be isolated
|
|
211
|
+
# in the segmentation:
|
|
212
|
+
border = 4
|
|
213
|
+
pwb = patch_size + border
|
|
214
|
+
|
|
215
|
+
fwhm2sigma = 2.0 * math.sqrt(2.0 * math.log(2.0))
|
|
216
|
+
|
|
217
|
+
ny, nx = data.shape
|
|
218
|
+
|
|
219
|
+
stars = []
|
|
220
|
+
|
|
221
|
+
for yc in range(border + p2, ny - pwb, pwb):
|
|
222
|
+
for xc in range(border + p2, nx - pwb, pwb):
|
|
223
|
+
sl = np.s_[yc - p2 : yc + p2 + 1, xc - p2 : xc + p2 + 1]
|
|
224
|
+
flux = 1.0 + 99.0 * np.random.random()
|
|
225
|
+
if np.random.random() > 0.7:
|
|
226
|
+
# uniform image
|
|
227
|
+
psf = np.full((patch_size, patch_size), flux / patch_area)
|
|
228
|
+
else:
|
|
229
|
+
# "star":
|
|
230
|
+
fwhm = 1.5 + 1.5 * np.random.random()
|
|
231
|
+
sigma = fwhm / fwhm2sigma
|
|
232
|
+
|
|
233
|
+
psf = flux * Gaussian2DKernel(sigma, x_size=patch_size, y_size=patch_size).array
|
|
234
|
+
weight = 0.6 + 0.4 * np.random.random((patch_size, patch_size))
|
|
235
|
+
wflux = (psf * weight).sum()
|
|
236
|
+
|
|
237
|
+
mean_noise = (0.05 + 0.35 * np.random.random()) * flux / patch_area
|
|
238
|
+
rdnoise = mean_noise * np.random.random((patch_size, patch_size))
|
|
239
|
+
|
|
240
|
+
data[sl] = psf
|
|
241
|
+
wht[sl] = weight
|
|
242
|
+
dq[sl] = 0
|
|
243
|
+
var_patch = psf + rdnoise
|
|
244
|
+
var[sl] = var_patch
|
|
245
|
+
stars.append((xc, yc, wflux, (var_patch * weight**2).sum(), sl))
|
|
246
|
+
|
|
247
|
+
return data, wht, dq, var, stars, wcs
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def test_drizzle_defaults():
|
|
251
|
+
n = 200
|
|
252
|
+
in_shape = (n, n)
|
|
253
|
+
|
|
254
|
+
# input coordinate grid:
|
|
255
|
+
y, x = np.indices(in_shape, dtype=np.float64)
|
|
256
|
+
|
|
257
|
+
# simulate data:
|
|
258
|
+
in_sci = np.ones(in_shape, dtype=np.float32)
|
|
259
|
+
in_wht = np.ones(in_shape, dtype=np.float32)
|
|
260
|
+
|
|
261
|
+
# create a Drizzle object using all default parameters (except for 'kernel')
|
|
262
|
+
driz = resample.Drizzle(
|
|
263
|
+
kernel="square",
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
assert driz.out_img is None
|
|
267
|
+
assert driz.out_wht is None
|
|
268
|
+
assert driz.out_ctx is None
|
|
269
|
+
assert driz.total_exptime == 0.0
|
|
270
|
+
|
|
271
|
+
driz.add_image(
|
|
272
|
+
in_sci,
|
|
273
|
+
exptime=1.0,
|
|
274
|
+
pixmap=np.dstack([x, y]),
|
|
275
|
+
weight_map=in_wht,
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
pixmap = np.dstack([x + 1, y + 2])
|
|
279
|
+
driz.add_image(
|
|
280
|
+
3 * in_sci,
|
|
281
|
+
exptime=1.0,
|
|
282
|
+
pixmap=pixmap,
|
|
283
|
+
weight_map=in_wht,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
assert driz.out_img[0, 0] == 1
|
|
287
|
+
assert driz.out_img[1, 0] == 1
|
|
288
|
+
assert driz.out_img[2, 0] == 1
|
|
289
|
+
assert driz.out_img[1, 1] == 1
|
|
290
|
+
assert driz.out_img[1, 2] == 1
|
|
291
|
+
assert (driz.out_img[2, 1] - 2.0) < 1.0e-14
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
@pytest.mark.parametrize(
|
|
295
|
+
"kernel,test_image_type,max_diff_atol",
|
|
296
|
+
[
|
|
297
|
+
("square", "point", 1.0e-5),
|
|
298
|
+
("square", "grid", 1.0e-5),
|
|
299
|
+
("point", "grid", 1.0e-5),
|
|
300
|
+
("turbo", "grid", 1.0e-5),
|
|
301
|
+
("lanczos3", "grid", 1.0e-5),
|
|
302
|
+
("gaussian", "grid", 2.0e-5),
|
|
303
|
+
],
|
|
304
|
+
)
|
|
305
|
+
def test_resample_kernel(tmpdir, kernel, test_image_type, max_diff_atol):
|
|
306
|
+
"""
|
|
307
|
+
Test do_driz square kernel with point
|
|
308
|
+
"""
|
|
309
|
+
output_difference = str(tmpdir.join(f"difference_{kernel}_{test_image_type}.txt"))
|
|
310
|
+
|
|
311
|
+
inwcs = wcs_from_file("j8bt06nyq_flt.fits", ext=1)
|
|
312
|
+
if test_image_type == "point":
|
|
313
|
+
insci = make_point_image(inwcs.array_shape, (500, 200), 100.0)
|
|
314
|
+
else:
|
|
315
|
+
insci = make_grid_image(inwcs.array_shape, 64, 100.0)
|
|
316
|
+
inwht = np.ones_like(insci)
|
|
317
|
+
output_wcs, template_data = wcs_from_file(
|
|
318
|
+
f"reference_{kernel}_{test_image_type}.fits", ext=1, return_data=True
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
pixmap = utils.calc_pixmap(
|
|
322
|
+
inwcs,
|
|
323
|
+
output_wcs,
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
if kernel == "point":
|
|
327
|
+
pscale_ratio = 1.0
|
|
328
|
+
else:
|
|
329
|
+
pscale_ratio = utils.estimate_pixel_scale_ratio(
|
|
330
|
+
inwcs,
|
|
331
|
+
output_wcs,
|
|
332
|
+
refpix_from=inwcs.wcs.crpix,
|
|
333
|
+
refpix_to=output_wcs.wcs.crpix,
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
# ignore previous pscale and compute it the old way (only to make
|
|
337
|
+
# tests work with old truth files and thus to show that new API gives
|
|
338
|
+
# same results when equal definitions of the pixel scale is used):
|
|
339
|
+
pscale_ratio = np.sqrt(
|
|
340
|
+
np.sum(output_wcs.wcs.pc**2, axis=0)[0] / np.sum(inwcs.wcs.cd**2, axis=0)[0]
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
driz = resample.Drizzle(
|
|
344
|
+
kernel=kernel,
|
|
345
|
+
out_shape=output_wcs.array_shape,
|
|
346
|
+
fillval=0.0,
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
if kernel in ["square", "turbo", "point"]:
|
|
350
|
+
driz.add_image(
|
|
351
|
+
insci,
|
|
352
|
+
exptime=1.0,
|
|
353
|
+
pixmap=pixmap,
|
|
354
|
+
weight_map=inwht,
|
|
355
|
+
iscale=pscale_ratio**2,
|
|
356
|
+
pixel_scale_ratio=pscale_ratio,
|
|
357
|
+
)
|
|
358
|
+
else:
|
|
359
|
+
with pytest.warns(Warning, match=f"Kernel '{kernel}' is not a flux-conserving kernel"):
|
|
360
|
+
driz.add_image(
|
|
361
|
+
insci,
|
|
362
|
+
exptime=1.0,
|
|
363
|
+
pixmap=pixmap,
|
|
364
|
+
weight_map=inwht,
|
|
365
|
+
iscale=pscale_ratio**2,
|
|
366
|
+
pixel_scale_ratio=pscale_ratio,
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
_, med_diff, max_diff = centroid_statistics(
|
|
370
|
+
f"{kernel} with {test_image_type}",
|
|
371
|
+
output_difference,
|
|
372
|
+
driz.out_img,
|
|
373
|
+
template_data,
|
|
374
|
+
30.0,
|
|
375
|
+
8,
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
assert med_diff < 1.0e-6
|
|
379
|
+
assert max_diff < max_diff_atol
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
@pytest.mark.parametrize(
|
|
383
|
+
"kernel,max_diff_atol",
|
|
384
|
+
[
|
|
385
|
+
("square", 1.0e-5),
|
|
386
|
+
("turbo", 1.0e-5),
|
|
387
|
+
],
|
|
388
|
+
)
|
|
389
|
+
def test_resample_kernel_image(tmpdir, kernel, max_diff_atol):
|
|
390
|
+
"""
|
|
391
|
+
Test do_driz square kernel with point
|
|
392
|
+
"""
|
|
393
|
+
inwcs, insci = wcs_from_file("j8bt06nyq_flt.fits", ext=1, return_data=True)
|
|
394
|
+
inwht = np.ones_like(insci)
|
|
395
|
+
|
|
396
|
+
outwcs, ref_sci, ref_ctx, ref_wht = wcs_from_file(
|
|
397
|
+
f"reference_{kernel}_image.fits", ext=1, return_data=["SCI", "CTX", "WHT"]
|
|
398
|
+
)
|
|
399
|
+
ref_ctx = np.array(ref_ctx, dtype=np.int32)
|
|
400
|
+
|
|
401
|
+
pixmap = utils.calc_pixmap(
|
|
402
|
+
inwcs,
|
|
403
|
+
outwcs,
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
pscale_ratio = np.sqrt(np.sum(outwcs.wcs.cd**2, axis=0)[0] / np.sum(inwcs.wcs.cd**2, axis=0)[0])
|
|
407
|
+
|
|
408
|
+
driz = resample.Drizzle(
|
|
409
|
+
kernel=kernel,
|
|
410
|
+
out_shape=ref_sci.shape,
|
|
411
|
+
fillval=0.0,
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
driz.add_image(
|
|
415
|
+
insci,
|
|
416
|
+
exptime=1.0,
|
|
417
|
+
pixmap=pixmap,
|
|
418
|
+
weight_map=inwht,
|
|
419
|
+
iscale=pscale_ratio**2,
|
|
420
|
+
pixel_scale_ratio=pscale_ratio,
|
|
421
|
+
)
|
|
422
|
+
outctx = driz.out_ctx[0]
|
|
423
|
+
|
|
424
|
+
# in order to avoid small differences in the staircase in the outline
|
|
425
|
+
# of the input image in the output grid, select a subset:
|
|
426
|
+
sl = np.s_[125:-125, 5:-5]
|
|
427
|
+
|
|
428
|
+
assert np.allclose(driz.out_img[sl], ref_sci[sl], atol=0, rtol=1.0e-6)
|
|
429
|
+
assert np.allclose(driz.out_wht[sl], ref_wht[sl], atol=0, rtol=1.0e-6)
|
|
430
|
+
assert np.all(outctx[sl] == ref_ctx[sl])
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
@pytest.mark.parametrize(
|
|
434
|
+
"kernel,fc",
|
|
435
|
+
[
|
|
436
|
+
("square", True),
|
|
437
|
+
("point", True),
|
|
438
|
+
("turbo", True),
|
|
439
|
+
("lanczos2", False),
|
|
440
|
+
("lanczos3", False),
|
|
441
|
+
("gaussian", False),
|
|
442
|
+
],
|
|
443
|
+
)
|
|
444
|
+
def test_zero_input_weight(kernel, fc):
|
|
445
|
+
"""
|
|
446
|
+
Test do_driz square kernel with grid
|
|
447
|
+
"""
|
|
448
|
+
# initialize input:
|
|
449
|
+
insci = np.ones((200, 400), dtype=np.float32)
|
|
450
|
+
inwht = np.ones((200, 400), dtype=np.float32)
|
|
451
|
+
inwht[:, 150:155] = 0
|
|
452
|
+
|
|
453
|
+
# initialize output:
|
|
454
|
+
outsci = np.zeros((210, 410), dtype=np.float32)
|
|
455
|
+
outwht = np.zeros((210, 410), dtype=np.float32)
|
|
456
|
+
outctx = np.zeros((210, 410), dtype=np.int32)
|
|
457
|
+
|
|
458
|
+
# define coordinate mapping:
|
|
459
|
+
pixmap = np.moveaxis(np.mgrid[1:201, 1:401][::-1], 0, -1)
|
|
460
|
+
|
|
461
|
+
# resample:
|
|
462
|
+
if fc:
|
|
463
|
+
cdrizzle.tdriz(
|
|
464
|
+
insci,
|
|
465
|
+
inwht,
|
|
466
|
+
pixmap,
|
|
467
|
+
outsci,
|
|
468
|
+
outwht,
|
|
469
|
+
outctx,
|
|
470
|
+
uniqid=1,
|
|
471
|
+
xmin=0,
|
|
472
|
+
xmax=400,
|
|
473
|
+
ymin=0,
|
|
474
|
+
ymax=200,
|
|
475
|
+
pixfrac=1,
|
|
476
|
+
kernel=kernel,
|
|
477
|
+
in_units="cps",
|
|
478
|
+
expscale=1,
|
|
479
|
+
wtscale=1,
|
|
480
|
+
fillstr="INDEF",
|
|
481
|
+
)
|
|
482
|
+
else:
|
|
483
|
+
with pytest.warns(Warning, match=f"Kernel '{kernel}' is not a flux-conserving kernel"):
|
|
484
|
+
cdrizzle.tdriz(
|
|
485
|
+
insci,
|
|
486
|
+
inwht,
|
|
487
|
+
pixmap,
|
|
488
|
+
outsci,
|
|
489
|
+
outwht,
|
|
490
|
+
outctx,
|
|
491
|
+
uniqid=1,
|
|
492
|
+
xmin=0,
|
|
493
|
+
xmax=400,
|
|
494
|
+
ymin=0,
|
|
495
|
+
ymax=200,
|
|
496
|
+
pixfrac=1,
|
|
497
|
+
kernel=kernel,
|
|
498
|
+
in_units="cps",
|
|
499
|
+
expscale=1,
|
|
500
|
+
wtscale=1,
|
|
501
|
+
fillstr="INDEF",
|
|
502
|
+
)
|
|
503
|
+
# pytest.xfail("Not a flux-conserving kernel")
|
|
504
|
+
|
|
505
|
+
# check that no pixel with 0 weight has any counts:
|
|
506
|
+
assert np.sum(np.abs(outsci[(outwht == 0)])) == 0.0
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
@pytest.mark.parametrize(
|
|
510
|
+
"interpolator,test_image_type",
|
|
511
|
+
[
|
|
512
|
+
("poly5", "point"),
|
|
513
|
+
("default", "grid"),
|
|
514
|
+
("lan3", "grid"),
|
|
515
|
+
("lan5", "grid"),
|
|
516
|
+
],
|
|
517
|
+
)
|
|
518
|
+
def test_blot_interpolation(tmpdir, interpolator, test_image_type):
|
|
519
|
+
"""
|
|
520
|
+
Test do_driz square kernel with point
|
|
521
|
+
"""
|
|
522
|
+
output_difference = str(tmpdir.join(f"difference_blot_{interpolator}_{test_image_type}.txt"))
|
|
523
|
+
|
|
524
|
+
outwcs = wcs_from_file("j8bt06nyq_flt.fits", ext=1)
|
|
525
|
+
if test_image_type == "point":
|
|
526
|
+
outsci = make_point_image(outwcs.array_shape, (500, 200), 40.0)
|
|
527
|
+
ref_fname = "reference_blot_point.fits"
|
|
528
|
+
else:
|
|
529
|
+
outsci = make_grid_image(outwcs.array_shape, 64, 100.0)
|
|
530
|
+
ref_fname = f"reference_blot_{interpolator}.fits"
|
|
531
|
+
inwcs, template_data = wcs_from_file(ref_fname, ext=1, return_data=True)
|
|
532
|
+
|
|
533
|
+
pixmap = utils.calc_pixmap(inwcs, outwcs)
|
|
534
|
+
|
|
535
|
+
# compute pscale the old way (only to make
|
|
536
|
+
# tests work with old truth files and thus to show that new API gives
|
|
537
|
+
# same results when equal definitions of the pixel scale is used):
|
|
538
|
+
pscale_ratio = np.sqrt(np.sum(inwcs.wcs.pc**2, axis=0)[0] / np.sum(outwcs.wcs.cd**2, axis=0)[0])
|
|
539
|
+
|
|
540
|
+
if interpolator == "default":
|
|
541
|
+
kwargs = {}
|
|
542
|
+
else:
|
|
543
|
+
kwargs = {"interp": interpolator}
|
|
544
|
+
|
|
545
|
+
blotted_image = resample.blot_image(
|
|
546
|
+
outsci, pixmap=pixmap, iscale=1.0 / (pscale_ratio**2), **kwargs
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
_, med_diff, max_diff = centroid_statistics(
|
|
550
|
+
"blot with '{interpolator}' and '{test_image_type}'",
|
|
551
|
+
output_difference,
|
|
552
|
+
blotted_image,
|
|
553
|
+
template_data,
|
|
554
|
+
20.0,
|
|
555
|
+
16,
|
|
556
|
+
)
|
|
557
|
+
assert med_diff < 1.0e-6
|
|
558
|
+
assert max_diff < 1.0e-5
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
def test_context_planes():
|
|
562
|
+
"""Reproduce error seen in issue #50"""
|
|
563
|
+
shape = (10, 10)
|
|
564
|
+
output_wcs = wcs.WCS()
|
|
565
|
+
output_wcs.wcs.ctype = ["RA---TAN", "DEC--TAN"]
|
|
566
|
+
output_wcs.wcs.pc = [[1, 0], [0, 1]]
|
|
567
|
+
output_wcs.pixel_shape = shape
|
|
568
|
+
driz = resample.Drizzle(out_shape=tuple(shape))
|
|
569
|
+
|
|
570
|
+
image = np.ones(shape)
|
|
571
|
+
inwcs = wcs.WCS()
|
|
572
|
+
inwcs.wcs.ctype = ["RA---TAN", "DEC--TAN"]
|
|
573
|
+
inwcs.wcs.cd = [[1, 0], [0, 1]]
|
|
574
|
+
inwcs.pixel_shape = shape
|
|
575
|
+
|
|
576
|
+
pixmap = utils.calc_pixmap(inwcs, output_wcs)
|
|
577
|
+
|
|
578
|
+
# context image must be 2D or 3D:
|
|
579
|
+
with pytest.raises(ValueError) as err_info:
|
|
580
|
+
resample.Drizzle(
|
|
581
|
+
kernel="point",
|
|
582
|
+
exptime=0.0,
|
|
583
|
+
out_shape=shape,
|
|
584
|
+
out_ctx=[0, 0, 0],
|
|
585
|
+
)
|
|
586
|
+
assert str(err_info.value).startswith("'out_ctx' must be either a 2D or 3D array.")
|
|
587
|
+
|
|
588
|
+
driz = resample.Drizzle(
|
|
589
|
+
kernel="square",
|
|
590
|
+
out_shape=output_wcs.array_shape,
|
|
591
|
+
fillval=0.0,
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
for i in range(32):
|
|
595
|
+
assert driz.ctx_id == i
|
|
596
|
+
driz.add_image(image, exptime=1.0, pixmap=pixmap)
|
|
597
|
+
assert driz.out_ctx.shape == (1, 10, 10)
|
|
598
|
+
|
|
599
|
+
driz.add_image(image, exptime=1.0, pixmap=pixmap)
|
|
600
|
+
assert driz.out_ctx.shape == (2, 10, 10)
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
def test_no_context_image():
|
|
604
|
+
"""Reproduce error seen in issue #50"""
|
|
605
|
+
shape = (10, 10)
|
|
606
|
+
output_wcs = wcs.WCS()
|
|
607
|
+
output_wcs.wcs.ctype = ["RA---TAN", "DEC--TAN"]
|
|
608
|
+
output_wcs.wcs.pc = [[1, 0], [0, 1]]
|
|
609
|
+
output_wcs.pixel_shape = shape
|
|
610
|
+
driz = resample.Drizzle(out_shape=tuple(shape), begin_ctx_id=-1, disable_ctx=True)
|
|
611
|
+
assert driz.out_ctx is None
|
|
612
|
+
assert driz.ctx_id is None
|
|
613
|
+
|
|
614
|
+
image = np.ones(shape)
|
|
615
|
+
inwcs = wcs.WCS()
|
|
616
|
+
inwcs.wcs.ctype = ["RA---TAN", "DEC--TAN"]
|
|
617
|
+
inwcs.wcs.cd = [[1, 0], [0, 1]]
|
|
618
|
+
inwcs.pixel_shape = shape
|
|
619
|
+
|
|
620
|
+
pixmap = utils.calc_pixmap(inwcs, output_wcs)
|
|
621
|
+
|
|
622
|
+
for i in range(33):
|
|
623
|
+
driz.add_image(image, exptime=1.0, pixmap=pixmap)
|
|
624
|
+
assert driz.out_ctx is None
|
|
625
|
+
assert driz.ctx_id is None
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
def test_init_ctx_id():
|
|
629
|
+
# starting context ID must be positive
|
|
630
|
+
with pytest.raises(ValueError) as err_info:
|
|
631
|
+
resample.Drizzle(
|
|
632
|
+
kernel="square",
|
|
633
|
+
exptime=0.0,
|
|
634
|
+
begin_ctx_id=-1,
|
|
635
|
+
out_shape=(10, 10),
|
|
636
|
+
)
|
|
637
|
+
assert str(err_info.value).startswith("Invalid context image ID")
|
|
638
|
+
|
|
639
|
+
with pytest.raises(ValueError) as err_info:
|
|
640
|
+
resample.Drizzle(
|
|
641
|
+
kernel="square",
|
|
642
|
+
exptime=0.0,
|
|
643
|
+
out_shape=(10, 10),
|
|
644
|
+
begin_ctx_id=1,
|
|
645
|
+
max_ctx_id=0,
|
|
646
|
+
)
|
|
647
|
+
assert str(err_info.value).startswith("'max_ctx_id' cannot be smaller than 'begin_ctx_id'.")
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
def test_context_agrees_with_weight():
|
|
651
|
+
n = 200
|
|
652
|
+
out_shape = (n, n)
|
|
653
|
+
|
|
654
|
+
# allocate output arrays:
|
|
655
|
+
out_img = np.zeros(out_shape, dtype=np.float32)
|
|
656
|
+
out_ctx = np.zeros(out_shape, dtype=np.int32)
|
|
657
|
+
out_wht = np.zeros(out_shape, dtype=np.float32)
|
|
658
|
+
|
|
659
|
+
# previous data in weight and context must agree:
|
|
660
|
+
with pytest.raises(ValueError) as err_info:
|
|
661
|
+
out_ctx[0, 0] = 1
|
|
662
|
+
out_ctx[0, 1] = 1
|
|
663
|
+
out_wht[0, 0] = 0.1
|
|
664
|
+
resample.Drizzle(
|
|
665
|
+
kernel="square",
|
|
666
|
+
out_shape=out_shape,
|
|
667
|
+
out_img=out_img,
|
|
668
|
+
out_ctx=out_ctx,
|
|
669
|
+
out_wht=out_wht,
|
|
670
|
+
exptime=1.0,
|
|
671
|
+
)
|
|
672
|
+
assert str(err_info.value).startswith(
|
|
673
|
+
"Inconsistent values of supplied 'out_wht' and 'out_ctx' "
|
|
674
|
+
)
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+
@pytest.mark.parametrize(
|
|
678
|
+
"kernel,fc,pixel_scale_ratio",
|
|
679
|
+
[
|
|
680
|
+
("square", True, 1.0),
|
|
681
|
+
("point", True, 1.0),
|
|
682
|
+
("turbo", True, 1.0),
|
|
683
|
+
("turbo", True, None),
|
|
684
|
+
("lanczos2", False, 1.0),
|
|
685
|
+
("lanczos2", False, None),
|
|
686
|
+
("lanczos3", False, 1.0),
|
|
687
|
+
("lanczos3", False, None),
|
|
688
|
+
("gaussian", False, 1.0),
|
|
689
|
+
("gaussian", False, None),
|
|
690
|
+
],
|
|
691
|
+
)
|
|
692
|
+
def test_flux_conservation_nondistorted(kernel, fc, pixel_scale_ratio):
|
|
693
|
+
n = 200
|
|
694
|
+
in_shape = (n, n)
|
|
695
|
+
|
|
696
|
+
# input coordinate grid:
|
|
697
|
+
y, x = np.indices(in_shape, dtype=np.float64)
|
|
698
|
+
|
|
699
|
+
# simulate a gaussian "star":
|
|
700
|
+
fwhm = 2.9
|
|
701
|
+
x0 = 50.0
|
|
702
|
+
y0 = 68.0
|
|
703
|
+
sig = fwhm / (2.0 * np.sqrt(2.0 * np.log(2.0 * fwhm)))
|
|
704
|
+
sig2 = sig * sig
|
|
705
|
+
star = np.exp(
|
|
706
|
+
-0.5 / sig2 * ((x.astype(np.float32) - x0) ** 2 + (y.astype(np.float32) - y0) ** 2)
|
|
707
|
+
)
|
|
708
|
+
in_sci = (star / np.sum(star)).astype(np.float32) # normalize to 1
|
|
709
|
+
in_wht = np.ones(in_shape, dtype=np.float32)
|
|
710
|
+
|
|
711
|
+
# linear shift:
|
|
712
|
+
xp = x + 0.5
|
|
713
|
+
yp = y + 0.2
|
|
714
|
+
|
|
715
|
+
pixmap = np.dstack([xp, yp])
|
|
716
|
+
|
|
717
|
+
out_shape = (int(yp.max()) + 1, int(xp.max()) + 1)
|
|
718
|
+
# make sure distorion is not moving flux out of the image towards negative
|
|
719
|
+
# coordinates (just because of the simple way of how we account for output
|
|
720
|
+
# image size)
|
|
721
|
+
assert np.min(xp) > -0.5 and np.min(yp) > -0.5
|
|
722
|
+
|
|
723
|
+
out_img = np.zeros(out_shape, dtype=np.float32)
|
|
724
|
+
out_ctx = np.zeros(out_shape, dtype=np.int32)
|
|
725
|
+
out_wht = np.zeros(out_shape, dtype=np.float32)
|
|
726
|
+
|
|
727
|
+
if fc:
|
|
728
|
+
cdrizzle.tdriz(
|
|
729
|
+
in_sci,
|
|
730
|
+
in_wht,
|
|
731
|
+
pixmap,
|
|
732
|
+
out_img,
|
|
733
|
+
out_wht,
|
|
734
|
+
out_ctx,
|
|
735
|
+
pixfrac=1.0,
|
|
736
|
+
pscale_ratio=pixel_scale_ratio,
|
|
737
|
+
kernel=kernel,
|
|
738
|
+
in_units="cps",
|
|
739
|
+
expscale=1.0,
|
|
740
|
+
wtscale=1.0,
|
|
741
|
+
)
|
|
742
|
+
else:
|
|
743
|
+
with pytest.warns(Warning, match=f"Kernel '{kernel}' is not a flux-conserving kernel"):
|
|
744
|
+
cdrizzle.tdriz(
|
|
745
|
+
in_sci,
|
|
746
|
+
in_wht,
|
|
747
|
+
pixmap,
|
|
748
|
+
out_img,
|
|
749
|
+
out_wht,
|
|
750
|
+
out_ctx,
|
|
751
|
+
pixfrac=1.0,
|
|
752
|
+
pscale_ratio=pixel_scale_ratio,
|
|
753
|
+
kernel=kernel,
|
|
754
|
+
in_units="cps",
|
|
755
|
+
expscale=1.0,
|
|
756
|
+
wtscale=1.0,
|
|
757
|
+
)
|
|
758
|
+
|
|
759
|
+
pytest.xfail("Not a flux-conserving kernel")
|
|
760
|
+
|
|
761
|
+
assert np.allclose(
|
|
762
|
+
np.sum(out_img * out_wht),
|
|
763
|
+
np.sum(in_sci),
|
|
764
|
+
atol=0.0,
|
|
765
|
+
rtol=0.0001,
|
|
766
|
+
)
|
|
767
|
+
|
|
768
|
+
|
|
769
|
+
@pytest.mark.parametrize(
|
|
770
|
+
"kernel,fc",
|
|
771
|
+
[
|
|
772
|
+
("square", True),
|
|
773
|
+
("point", True),
|
|
774
|
+
("turbo", True),
|
|
775
|
+
("lanczos2", False),
|
|
776
|
+
("lanczos3", False),
|
|
777
|
+
("gaussian", False),
|
|
778
|
+
],
|
|
779
|
+
)
|
|
780
|
+
def test_flux_conservation_distorted(kernel, fc):
|
|
781
|
+
n = 200
|
|
782
|
+
in_shape = (n, n)
|
|
783
|
+
|
|
784
|
+
# input coordinate grid:
|
|
785
|
+
y, x = np.indices(in_shape, dtype=np.float64)
|
|
786
|
+
|
|
787
|
+
# simulate a gaussian "star":
|
|
788
|
+
fwhm = 2.9
|
|
789
|
+
x0 = 50.0
|
|
790
|
+
y0 = 68.0
|
|
791
|
+
sig = fwhm / (2.0 * np.sqrt(2.0 * np.log(2.0 * fwhm)))
|
|
792
|
+
sig2 = sig * sig
|
|
793
|
+
star = np.exp(
|
|
794
|
+
-0.5 / sig2 * ((x.astype(np.float32) - x0) ** 2 + (y.astype(np.float32) - y0) ** 2)
|
|
795
|
+
)
|
|
796
|
+
in_sci = (star / np.sum(star)).astype(np.float32) # normalize to 1
|
|
797
|
+
in_wht = np.ones(in_shape, dtype=np.float32)
|
|
798
|
+
|
|
799
|
+
# linear shift:
|
|
800
|
+
xp = x + 0.5
|
|
801
|
+
yp = y + 0.2
|
|
802
|
+
# add distortion:
|
|
803
|
+
xp += 1e-4 * x**2 + 1e-5 * x * y
|
|
804
|
+
yp += 1e-3 * y**2 - 2e-5 * x * y
|
|
805
|
+
|
|
806
|
+
pixmap = np.dstack([xp, yp])
|
|
807
|
+
|
|
808
|
+
out_shape = (int(yp.max()) + 1, int(xp.max()) + 1)
|
|
809
|
+
# make sure distorion is not moving (pixels with) flux out of the image
|
|
810
|
+
# towards negative coordinates (just because of the simple way of how we
|
|
811
|
+
# account for output image size):
|
|
812
|
+
assert np.min(xp) > -0.5 and np.min(yp) > -0.5
|
|
813
|
+
|
|
814
|
+
out_img = np.zeros(out_shape, dtype=np.float32)
|
|
815
|
+
out_ctx = np.zeros(out_shape, dtype=np.int32)
|
|
816
|
+
out_wht = np.zeros(out_shape, dtype=np.float32)
|
|
817
|
+
|
|
818
|
+
if fc:
|
|
819
|
+
cdrizzle.tdriz(
|
|
820
|
+
in_sci,
|
|
821
|
+
in_wht,
|
|
822
|
+
pixmap,
|
|
823
|
+
out_img,
|
|
824
|
+
out_wht,
|
|
825
|
+
out_ctx,
|
|
826
|
+
pixfrac=1.0,
|
|
827
|
+
pscale_ratio=1.0,
|
|
828
|
+
kernel=kernel,
|
|
829
|
+
in_units="cps",
|
|
830
|
+
expscale=1.0,
|
|
831
|
+
wtscale=1.0,
|
|
832
|
+
)
|
|
833
|
+
else:
|
|
834
|
+
with pytest.warns(Warning, match=f"Kernel '{kernel}' is not a flux-conserving kernel"):
|
|
835
|
+
cdrizzle.tdriz(
|
|
836
|
+
in_sci,
|
|
837
|
+
in_wht,
|
|
838
|
+
pixmap,
|
|
839
|
+
out_img,
|
|
840
|
+
out_wht,
|
|
841
|
+
out_ctx,
|
|
842
|
+
pixfrac=1.0,
|
|
843
|
+
pscale_ratio=1.0,
|
|
844
|
+
kernel=kernel,
|
|
845
|
+
in_units="cps",
|
|
846
|
+
expscale=1.0,
|
|
847
|
+
wtscale=1.0,
|
|
848
|
+
)
|
|
849
|
+
pytest.xfail("Not a flux-conserving kernel")
|
|
850
|
+
|
|
851
|
+
assert np.allclose(
|
|
852
|
+
np.sum(out_img * out_wht),
|
|
853
|
+
np.sum(in_sci),
|
|
854
|
+
atol=0.0,
|
|
855
|
+
rtol=0.0001,
|
|
856
|
+
)
|
|
857
|
+
|
|
858
|
+
|
|
859
|
+
@pytest.mark.parametrize("kernel", ["square", "turbo", "point"])
|
|
860
|
+
@pytest.mark.parametrize("pscale_ratio", [0.55, 1.0, 1.2])
|
|
861
|
+
def test_flux_conservation_distorted_distributed_sources(nrcb5_stars, kernel, pscale_ratio):
|
|
862
|
+
"""test aperture photometry"""
|
|
863
|
+
insci, inwht, _, invar, stars, wcs = nrcb5_stars
|
|
864
|
+
|
|
865
|
+
suffix = f"{pscale_ratio}".replace(".", "p")
|
|
866
|
+
output_wcs = wcs_from_file(f"nrcb5_output_wcs_psr_{suffix}.hdr")
|
|
867
|
+
|
|
868
|
+
pixmap = utils.calc_pixmap(
|
|
869
|
+
wcs,
|
|
870
|
+
output_wcs,
|
|
871
|
+
wcs.array_shape,
|
|
872
|
+
)
|
|
873
|
+
|
|
874
|
+
# resample variance using squared coefficients AND a "point" kernel
|
|
875
|
+
# ("point" kernel is needed for the *aperture* photometry/variance)
|
|
876
|
+
driz_var = resample.Drizzle(
|
|
877
|
+
kernel="point",
|
|
878
|
+
out_shape=output_wcs.array_shape,
|
|
879
|
+
fillval=0.0,
|
|
880
|
+
)
|
|
881
|
+
driz_var.add_image(
|
|
882
|
+
data=insci,
|
|
883
|
+
data2=invar,
|
|
884
|
+
exptime=1.0,
|
|
885
|
+
pixmap=pixmap,
|
|
886
|
+
weight_map=inwht,
|
|
887
|
+
iscale=1.0,
|
|
888
|
+
pixel_scale_ratio=1.0,
|
|
889
|
+
)
|
|
890
|
+
|
|
891
|
+
# for efficiency, instead of doing this patch-by-patch,
|
|
892
|
+
# multiply resampled data by resampled image weight
|
|
893
|
+
if kernel == "point":
|
|
894
|
+
out_data = driz_var.out_img * driz_var.out_wht
|
|
895
|
+
else:
|
|
896
|
+
# resample "SCI" array using user-specified kernel:
|
|
897
|
+
driz = resample.Drizzle(
|
|
898
|
+
kernel=kernel,
|
|
899
|
+
out_shape=output_wcs.array_shape,
|
|
900
|
+
fillval=0.0,
|
|
901
|
+
)
|
|
902
|
+
driz.add_image(
|
|
903
|
+
data=insci,
|
|
904
|
+
exptime=1.0,
|
|
905
|
+
pixmap=pixmap,
|
|
906
|
+
weight_map=inwht,
|
|
907
|
+
iscale=1.0,
|
|
908
|
+
pixel_scale_ratio=1.0,
|
|
909
|
+
)
|
|
910
|
+
out_data = driz_var.out_img * driz_var.out_wht
|
|
911
|
+
out_var = driz_var.out_img2[0] * (driz_var.out_wht**2)
|
|
912
|
+
|
|
913
|
+
dim3 = (slice(None, None, None),)
|
|
914
|
+
|
|
915
|
+
for _, _, wfin, wvfin, sl in stars:
|
|
916
|
+
xyout = pixmap[sl + dim3]
|
|
917
|
+
xmin = math.floor(xyout[:, :, 0].min() - 0.5)
|
|
918
|
+
xmax = math.ceil(xyout[:, :, 0].max() + 1.5)
|
|
919
|
+
ymin = math.floor(xyout[:, :, 1].min() - 0.5)
|
|
920
|
+
ymax = math.ceil(xyout[:, :, 1].max() + 1.5)
|
|
921
|
+
|
|
922
|
+
wfout = np.nansum(out_data[ymin:ymax, xmin:xmax])
|
|
923
|
+
wvfout = np.nansum(out_var[ymin:ymax, xmin:xmax])
|
|
924
|
+
|
|
925
|
+
# test resampled "weighted" flux in an aperture matches flux from input
|
|
926
|
+
assert np.allclose(wfin, wfout, rtol=1.0e-6, atol=0.0)
|
|
927
|
+
|
|
928
|
+
# test resampled "weighted" variance in an aperture matches
|
|
929
|
+
# "weighted" variance from input
|
|
930
|
+
assert np.allclose(wvfin, wvfout, rtol=1.0e-6, atol=0.0)
|
|
931
|
+
|
|
932
|
+
|
|
933
|
+
def test_drizzle_exptime():
|
|
934
|
+
n = 200
|
|
935
|
+
in_shape = (n, n)
|
|
936
|
+
|
|
937
|
+
# input coordinate grid:
|
|
938
|
+
y, x = np.indices(in_shape, dtype=np.float64)
|
|
939
|
+
|
|
940
|
+
# simulate data:
|
|
941
|
+
in_sci = np.ones(in_shape, dtype=np.float32)
|
|
942
|
+
in_wht = np.ones(in_shape, dtype=np.float32)
|
|
943
|
+
|
|
944
|
+
pixmap = np.dstack([x, y])
|
|
945
|
+
|
|
946
|
+
# allocate output arrays:
|
|
947
|
+
out_shape = (int(y.max()) + 1, int(x.max()) + 1)
|
|
948
|
+
out_img = np.zeros(out_shape, dtype=np.float32)
|
|
949
|
+
out_ctx = np.zeros(out_shape, dtype=np.int32)
|
|
950
|
+
out_wht = np.zeros(out_shape, dtype=np.float32)
|
|
951
|
+
|
|
952
|
+
# starting exposure time must be non-negative:
|
|
953
|
+
with pytest.raises(ValueError) as err_info:
|
|
954
|
+
driz = resample.Drizzle(
|
|
955
|
+
kernel="square",
|
|
956
|
+
out_shape=out_shape,
|
|
957
|
+
fillval="indef",
|
|
958
|
+
exptime=-1.0,
|
|
959
|
+
)
|
|
960
|
+
assert str(err_info.value) == "Exposure time must be non-negative."
|
|
961
|
+
|
|
962
|
+
driz = resample.Drizzle(
|
|
963
|
+
kernel="turbo",
|
|
964
|
+
out_shape=out_shape,
|
|
965
|
+
fillval="",
|
|
966
|
+
out_img=out_img,
|
|
967
|
+
out_ctx=out_ctx,
|
|
968
|
+
out_wht=out_wht,
|
|
969
|
+
exptime=1.0,
|
|
970
|
+
)
|
|
971
|
+
assert driz.kernel == "turbo"
|
|
972
|
+
|
|
973
|
+
driz.add_image(in_sci, weight_map=in_wht, exptime=1.03456, pixmap=pixmap)
|
|
974
|
+
assert np.allclose(driz.total_exptime, 2.03456, rtol=0, atol=1.0e-14)
|
|
975
|
+
|
|
976
|
+
driz.add_image(in_sci, weight_map=in_wht, exptime=3.1415926, pixmap=pixmap)
|
|
977
|
+
assert np.allclose(driz.total_exptime, 5.1761526, rtol=0, atol=1.0e-14)
|
|
978
|
+
|
|
979
|
+
with pytest.raises(ValueError) as err_info:
|
|
980
|
+
driz.add_image(in_sci, weight_map=in_wht, exptime=-1, pixmap=pixmap)
|
|
981
|
+
assert str(err_info.value) == "'exptime' *must* be a strictly positive number."
|
|
982
|
+
|
|
983
|
+
# exptime cannot be 0 when output data has data:
|
|
984
|
+
with pytest.raises(ValueError) as err_info:
|
|
985
|
+
out_ctx[0, 0] = 1
|
|
986
|
+
driz = resample.Drizzle(
|
|
987
|
+
kernel="square",
|
|
988
|
+
out_shape=out_shape,
|
|
989
|
+
fillval="indef",
|
|
990
|
+
out_img=out_img,
|
|
991
|
+
out_ctx=out_ctx,
|
|
992
|
+
out_wht=out_wht,
|
|
993
|
+
exptime=0.0,
|
|
994
|
+
)
|
|
995
|
+
assert str(err_info.value).startswith(
|
|
996
|
+
"Inconsistent exposure time and context and/or weight images:"
|
|
997
|
+
)
|
|
998
|
+
|
|
999
|
+
# exptime must be 0 when output arrays are not provided:
|
|
1000
|
+
with pytest.raises(ValueError) as err_info:
|
|
1001
|
+
driz = resample.Drizzle(
|
|
1002
|
+
kernel="square",
|
|
1003
|
+
out_shape=out_shape,
|
|
1004
|
+
exptime=1.0,
|
|
1005
|
+
)
|
|
1006
|
+
assert str(err_info.value).startswith("Exposure time must be 0.0 for the first resampling")
|
|
1007
|
+
|
|
1008
|
+
|
|
1009
|
+
def test_drizzle_unsupported_kernel():
|
|
1010
|
+
with pytest.raises(ValueError) as err_info:
|
|
1011
|
+
resample.Drizzle(
|
|
1012
|
+
kernel="magic_image_improver",
|
|
1013
|
+
out_shape=(10, 10),
|
|
1014
|
+
exptime=0.0,
|
|
1015
|
+
)
|
|
1016
|
+
assert str(err_info.value) == "Kernel 'magic_image_improver' is not supported."
|
|
1017
|
+
|
|
1018
|
+
|
|
1019
|
+
def test_pixmap_shape_matches_image():
|
|
1020
|
+
n = 200
|
|
1021
|
+
in_shape = (n, n)
|
|
1022
|
+
|
|
1023
|
+
# input coordinate grid:
|
|
1024
|
+
y, x = np.indices((n + 1, n), dtype=np.float64)
|
|
1025
|
+
|
|
1026
|
+
# simulate data:
|
|
1027
|
+
in_sci = np.ones(in_shape, dtype=np.float32)
|
|
1028
|
+
in_wht = np.ones(in_shape, dtype=np.float32)
|
|
1029
|
+
|
|
1030
|
+
pixmap = np.dstack([x, y])
|
|
1031
|
+
|
|
1032
|
+
driz = resample.Drizzle(
|
|
1033
|
+
kernel="square",
|
|
1034
|
+
fillval=0.0,
|
|
1035
|
+
exptime=0.0,
|
|
1036
|
+
)
|
|
1037
|
+
|
|
1038
|
+
# last two sizes of the pixelmap must match those of input images:
|
|
1039
|
+
with pytest.raises(ValueError) as err_info:
|
|
1040
|
+
driz.add_image(
|
|
1041
|
+
in_sci,
|
|
1042
|
+
exptime=1.0,
|
|
1043
|
+
pixmap=pixmap,
|
|
1044
|
+
weight_map=in_wht,
|
|
1045
|
+
iscale=1.0,
|
|
1046
|
+
pixel_scale_ratio=1.0,
|
|
1047
|
+
)
|
|
1048
|
+
assert str(err_info.value) == "'pixmap' shape is not consistent with 'data' shape."
|
|
1049
|
+
|
|
1050
|
+
|
|
1051
|
+
def test_drizzle_fillval():
|
|
1052
|
+
n = 200
|
|
1053
|
+
in_shape = (n, n)
|
|
1054
|
+
|
|
1055
|
+
# input coordinate grid:
|
|
1056
|
+
y, x = np.indices(in_shape, dtype=np.float64)
|
|
1057
|
+
|
|
1058
|
+
# simulate a gaussian "star":
|
|
1059
|
+
fwhm = 2.9
|
|
1060
|
+
x0 = 50.0
|
|
1061
|
+
y0 = 68.0
|
|
1062
|
+
sig = fwhm / (2.0 * np.sqrt(2.0 * np.log(2.0 * fwhm)))
|
|
1063
|
+
sig2 = sig * sig
|
|
1064
|
+
star = np.exp(
|
|
1065
|
+
-0.5 / sig2 * ((x.astype(np.float32) - x0) ** 2 + (y.astype(np.float32) - y0) ** 2)
|
|
1066
|
+
)
|
|
1067
|
+
in_sci = (star / np.sum(star)).astype(np.float32) # normalize to 1
|
|
1068
|
+
in_wht = np.zeros(in_shape, dtype=np.float32)
|
|
1069
|
+
mask = np.where((x.astype(np.float32) - x0) ** 2 + (y.astype(np.float32) - y0) ** 2 <= 10)
|
|
1070
|
+
in_wht[mask] = 1.0
|
|
1071
|
+
|
|
1072
|
+
# linear shift:
|
|
1073
|
+
xp = x + 50
|
|
1074
|
+
yp = y + 50
|
|
1075
|
+
|
|
1076
|
+
pixmap = np.dstack([xp, yp])
|
|
1077
|
+
|
|
1078
|
+
out_shape = (int(yp.max()) + 1, int(xp.max()) + 1)
|
|
1079
|
+
# make sure distorion is not moving flux out of the image towards negative
|
|
1080
|
+
# coordinates (just because of the simple way of how we account for output
|
|
1081
|
+
# image size)
|
|
1082
|
+
assert np.min(xp) > -0.5 and np.min(yp) > -0.5
|
|
1083
|
+
|
|
1084
|
+
out_img = np.zeros(out_shape, dtype=np.float32) - 1.11
|
|
1085
|
+
out_ctx = np.zeros((1,) + out_shape, dtype=np.int32)
|
|
1086
|
+
out_wht = np.zeros(out_shape, dtype=np.float32)
|
|
1087
|
+
|
|
1088
|
+
driz = resample.Drizzle(
|
|
1089
|
+
kernel="square",
|
|
1090
|
+
out_shape=out_shape,
|
|
1091
|
+
fillval="indef",
|
|
1092
|
+
exptime=0.0,
|
|
1093
|
+
)
|
|
1094
|
+
|
|
1095
|
+
driz.add_image(in_sci, weight_map=in_wht, exptime=1.0, pixmap=pixmap)
|
|
1096
|
+
assert np.isnan(driz.out_img[0, 0])
|
|
1097
|
+
assert driz.out_img[int(y0) + 50, int(x0) + 50] > 0.0
|
|
1098
|
+
|
|
1099
|
+
driz = resample.Drizzle(
|
|
1100
|
+
kernel="square",
|
|
1101
|
+
out_shape=out_shape,
|
|
1102
|
+
fillval="-1.11",
|
|
1103
|
+
out_img=out_img.copy(),
|
|
1104
|
+
out_ctx=out_ctx.copy(),
|
|
1105
|
+
out_wht=out_wht.copy(),
|
|
1106
|
+
exptime=0.0,
|
|
1107
|
+
)
|
|
1108
|
+
driz.add_image(in_sci, weight_map=in_wht, exptime=1.0, pixmap=pixmap)
|
|
1109
|
+
assert np.allclose(driz.out_img[0, 0], -1.11, rtol=0.0, atol=1.0e-7)
|
|
1110
|
+
assert driz.out_img[int(y0) + 50, int(x0) + 50] > 0.0
|
|
1111
|
+
assert set(driz.out_ctx.ravel().tolist()) == {0, 1}
|
|
1112
|
+
|
|
1113
|
+
# test same with numeric fillval:
|
|
1114
|
+
driz = resample.Drizzle(
|
|
1115
|
+
kernel="square",
|
|
1116
|
+
out_shape=out_shape,
|
|
1117
|
+
fillval=-1.11,
|
|
1118
|
+
out_img=out_img.copy(),
|
|
1119
|
+
out_ctx=out_ctx.copy(),
|
|
1120
|
+
out_wht=out_wht.copy(),
|
|
1121
|
+
exptime=0.0,
|
|
1122
|
+
)
|
|
1123
|
+
driz.add_image(in_sci, weight_map=in_wht, exptime=1.0, pixmap=pixmap)
|
|
1124
|
+
assert np.allclose(driz.out_img[0, 0], -1.11, rtol=0.0, atol=1.0e-7)
|
|
1125
|
+
assert np.allclose(float(driz.fillval), -1.11, rtol=0.0, atol=np.finfo(float).eps)
|
|
1126
|
+
|
|
1127
|
+
# make sure code raises exception for unsupported fillval:
|
|
1128
|
+
with pytest.raises(ValueError) as err_info:
|
|
1129
|
+
resample.Drizzle(
|
|
1130
|
+
kernel="square",
|
|
1131
|
+
out_shape=out_shape,
|
|
1132
|
+
fillval="fillval",
|
|
1133
|
+
exptime=0.0,
|
|
1134
|
+
)
|
|
1135
|
+
assert str(err_info.value) == "could not convert string to float: 'fillval'"
|
|
1136
|
+
|
|
1137
|
+
|
|
1138
|
+
def test_resample_get_shape_from_pixmap():
|
|
1139
|
+
n = 200
|
|
1140
|
+
in_shape = (n, n)
|
|
1141
|
+
|
|
1142
|
+
# input coordinate grid:
|
|
1143
|
+
y, x = np.indices(in_shape, dtype=np.float64)
|
|
1144
|
+
|
|
1145
|
+
# simulate constant data:
|
|
1146
|
+
in_sci = np.ones(in_shape, dtype=np.float32)
|
|
1147
|
+
in_wht = np.ones(in_shape, dtype=np.float32)
|
|
1148
|
+
|
|
1149
|
+
pixmap = np.dstack([x, y])
|
|
1150
|
+
|
|
1151
|
+
driz = resample.Drizzle(
|
|
1152
|
+
kernel="point",
|
|
1153
|
+
exptime=0.0,
|
|
1154
|
+
)
|
|
1155
|
+
|
|
1156
|
+
driz.add_image(in_sci, weight_map=in_wht, exptime=0.1, pixmap=pixmap)
|
|
1157
|
+
assert driz.out_img.shape == in_shape
|
|
1158
|
+
|
|
1159
|
+
|
|
1160
|
+
def test_resample_counts_units():
|
|
1161
|
+
n = 200
|
|
1162
|
+
in_shape = (n, n)
|
|
1163
|
+
|
|
1164
|
+
# input coordinate grid:
|
|
1165
|
+
y, x = np.indices(in_shape, dtype=np.float64)
|
|
1166
|
+
pixmap = np.dstack([x, y])
|
|
1167
|
+
|
|
1168
|
+
# simulate constant data:
|
|
1169
|
+
in_sci = np.ones(in_shape, dtype=np.float32)
|
|
1170
|
+
in_wht = np.ones(in_shape, dtype=np.float32)
|
|
1171
|
+
|
|
1172
|
+
driz = resample.Drizzle()
|
|
1173
|
+
driz.add_image(in_sci, weight_map=in_wht, exptime=1.0, pixmap=pixmap, in_units="cps")
|
|
1174
|
+
cps_max_val = driz.out_img.max()
|
|
1175
|
+
|
|
1176
|
+
driz = resample.Drizzle()
|
|
1177
|
+
driz.add_image(in_sci, weight_map=in_wht, exptime=2.0, pixmap=pixmap, in_units="counts")
|
|
1178
|
+
counts_max_val = driz.out_img.max()
|
|
1179
|
+
|
|
1180
|
+
assert abs(counts_max_val - cps_max_val / 2.0) < 1.0e-14
|
|
1181
|
+
|
|
1182
|
+
|
|
1183
|
+
def test_resample_inconsistent_output():
|
|
1184
|
+
n = 200
|
|
1185
|
+
out_shape = (n, n)
|
|
1186
|
+
|
|
1187
|
+
# different shapes:
|
|
1188
|
+
out_img = np.zeros((n, n), dtype=np.float32)
|
|
1189
|
+
out_ctx = np.zeros((1, n, n + 1), dtype=np.int32)
|
|
1190
|
+
out_wht = np.zeros((n + 1, n + 1), dtype=np.float32)
|
|
1191
|
+
|
|
1192
|
+
# shape from out_img:
|
|
1193
|
+
driz = resample.Drizzle(
|
|
1194
|
+
kernel="point",
|
|
1195
|
+
exptime=0.0,
|
|
1196
|
+
out_img=out_img,
|
|
1197
|
+
)
|
|
1198
|
+
assert driz.out_img.shape == out_shape
|
|
1199
|
+
|
|
1200
|
+
# inconsistent shapes:
|
|
1201
|
+
out_shape = (n + 1, n)
|
|
1202
|
+
with pytest.raises(ValueError) as err_info:
|
|
1203
|
+
resample.Drizzle(
|
|
1204
|
+
kernel="point",
|
|
1205
|
+
exptime=0.0,
|
|
1206
|
+
out_shape=out_shape,
|
|
1207
|
+
out_img=out_img,
|
|
1208
|
+
out_ctx=out_ctx,
|
|
1209
|
+
out_wht=out_wht,
|
|
1210
|
+
)
|
|
1211
|
+
assert str(err_info.value).startswith("Inconsistent data shapes specified")
|
|
1212
|
+
|
|
1213
|
+
|
|
1214
|
+
def test_resample_disable_ctx():
|
|
1215
|
+
n = 20
|
|
1216
|
+
in_shape = (n, n)
|
|
1217
|
+
|
|
1218
|
+
pixmap = np.dstack(np.indices(in_shape, dtype=np.float64)[::-1])
|
|
1219
|
+
|
|
1220
|
+
# simulate constant data:
|
|
1221
|
+
in_sci = np.ones(in_shape, dtype=np.float32)
|
|
1222
|
+
|
|
1223
|
+
driz = resample.Drizzle(
|
|
1224
|
+
disable_ctx=True,
|
|
1225
|
+
)
|
|
1226
|
+
|
|
1227
|
+
driz.add_image(in_sci, exptime=1.0, pixmap=pixmap)
|
|
1228
|
+
|
|
1229
|
+
|
|
1230
|
+
@pytest.mark.parametrize("fillval", ["NaN", "INDEF", "", None])
|
|
1231
|
+
def test_nan_fillval(fillval):
|
|
1232
|
+
driz = resample.Drizzle(kernel="square", fillval=fillval, out_shape=(20, 20))
|
|
1233
|
+
|
|
1234
|
+
assert np.all(np.isnan(driz.out_img))
|
|
1235
|
+
|
|
1236
|
+
|
|
1237
|
+
def test_resample_edge_sgarea_bug():
|
|
1238
|
+
"""
|
|
1239
|
+
Test from https://github.com/spacetelescope/drizzle/issues/187
|
|
1240
|
+
|
|
1241
|
+
"""
|
|
1242
|
+
pixmap = np.array(
|
|
1243
|
+
[
|
|
1244
|
+
[
|
|
1245
|
+
[0.31887051, 1.0],
|
|
1246
|
+
[1.01898591, 1.0],
|
|
1247
|
+
[1.71909665, 1.0],
|
|
1248
|
+
],
|
|
1249
|
+
[
|
|
1250
|
+
[0.31591881, 0.0],
|
|
1251
|
+
[1.0160342312345672, 0.0],
|
|
1252
|
+
[1.716145, 0.0],
|
|
1253
|
+
],
|
|
1254
|
+
],
|
|
1255
|
+
dtype="f8",
|
|
1256
|
+
)
|
|
1257
|
+
|
|
1258
|
+
in_shape = pixmap.shape[:2]
|
|
1259
|
+
img = np.full(in_shape, 42, dtype=np.float32)
|
|
1260
|
+
out_shape = (4, 4)
|
|
1261
|
+
|
|
1262
|
+
driz = resample.Drizzle(
|
|
1263
|
+
kernel="square",
|
|
1264
|
+
fillval="nan",
|
|
1265
|
+
out_shape=out_shape,
|
|
1266
|
+
disable_ctx=True,
|
|
1267
|
+
)
|
|
1268
|
+
|
|
1269
|
+
driz.add_image(
|
|
1270
|
+
img,
|
|
1271
|
+
exptime=11.776,
|
|
1272
|
+
in_units="cps",
|
|
1273
|
+
pixfrac=1.0,
|
|
1274
|
+
pixmap=pixmap,
|
|
1275
|
+
iscale=1.0,
|
|
1276
|
+
pixel_scale_ratio=1.0,
|
|
1277
|
+
wht_scale=1.0,
|
|
1278
|
+
)
|
|
1279
|
+
# expected pixels should be close to 42
|
|
1280
|
+
np.testing.assert_allclose(driz.out_img[:2, :3], img[0, 0], rtol=1e-6)
|
|
1281
|
+
|
|
1282
|
+
# other values should be nan
|
|
1283
|
+
np.testing.assert_equal(driz.out_img[:, 3:], np.nan)
|
|
1284
|
+
np.testing.assert_equal(driz.out_img[2:], np.nan)
|
|
1285
|
+
|
|
1286
|
+
|
|
1287
|
+
def test_resample_edge_collinear():
|
|
1288
|
+
"""
|
|
1289
|
+
Test that resample does not crash when the input image is smaller than the
|
|
1290
|
+
output image, and the edges of the two images are nearly collinear.
|
|
1291
|
+
|
|
1292
|
+
Test based on the example from
|
|
1293
|
+
https://github.com/spacetelescope/drizzle/issues/189#issue-3196294879
|
|
1294
|
+
|
|
1295
|
+
"""
|
|
1296
|
+
pixmap = np.array(
|
|
1297
|
+
[
|
|
1298
|
+
[
|
|
1299
|
+
[0.31, 1.0],
|
|
1300
|
+
[1.01, 1.0],
|
|
1301
|
+
[2.01, 1.0],
|
|
1302
|
+
],
|
|
1303
|
+
[
|
|
1304
|
+
[0.31, 0.0],
|
|
1305
|
+
[1.01, 0.0],
|
|
1306
|
+
[1.71, 0.0],
|
|
1307
|
+
],
|
|
1308
|
+
],
|
|
1309
|
+
dtype="f8",
|
|
1310
|
+
)
|
|
1311
|
+
|
|
1312
|
+
in_shape = pixmap.shape[:2]
|
|
1313
|
+
img = np.full(in_shape, np.pi, dtype=np.float32)
|
|
1314
|
+
in_flux = np.sum(img)
|
|
1315
|
+
out_shape = (4, 4)
|
|
1316
|
+
|
|
1317
|
+
driz = resample.Drizzle(
|
|
1318
|
+
kernel="square",
|
|
1319
|
+
fillval="nan",
|
|
1320
|
+
out_shape=out_shape,
|
|
1321
|
+
disable_ctx=True,
|
|
1322
|
+
)
|
|
1323
|
+
|
|
1324
|
+
driz.add_image(
|
|
1325
|
+
img,
|
|
1326
|
+
exptime=11.776,
|
|
1327
|
+
in_units="cps",
|
|
1328
|
+
pixfrac=1.0,
|
|
1329
|
+
pixmap=pixmap,
|
|
1330
|
+
iscale=1.0,
|
|
1331
|
+
pixel_scale_ratio=1.0,
|
|
1332
|
+
wht_scale=1.0,
|
|
1333
|
+
)
|
|
1334
|
+
|
|
1335
|
+
out_flux = np.nansum(driz.out_img * driz.out_wht)
|
|
1336
|
+
|
|
1337
|
+
# Given this pixmap, the entire input image should fit within the output
|
|
1338
|
+
# image. There should be at least 7 pixels with finite values in the output
|
|
1339
|
+
# image. We can get more than 7 pixels with finite values due to rounding
|
|
1340
|
+
# errors when computing polygon intersections (those "extra" pixels should)
|
|
1341
|
+
# have very small weights.
|
|
1342
|
+
assert np.sum(driz.out_wht > 1e-30) == 7
|
|
1343
|
+
assert np.sum(np.isfinite(driz.out_img)) >= 7
|
|
1344
|
+
# output image intensity must be equal to the input image intensity:
|
|
1345
|
+
assert np.allclose(driz.out_img[np.isfinite(driz.out_img)], img[0, 0], rtol=0, atol=1e-6)
|
|
1346
|
+
# flux in the output image should be equal to the flux in the input image:
|
|
1347
|
+
assert np.allclose(out_flux, in_flux, rtol=1e-6, atol=0.0)
|
|
1348
|
+
# area of the signal in the input image:
|
|
1349
|
+
assert np.allclose(np.sum(driz.out_wht), 6.0, rtol=0, atol=1.0e-6)
|
|
1350
|
+
|
|
1351
|
+
|
|
1352
|
+
@pytest.mark.parametrize(
|
|
1353
|
+
"kernel,fc",
|
|
1354
|
+
[
|
|
1355
|
+
("square", True),
|
|
1356
|
+
("point", True),
|
|
1357
|
+
("turbo", True),
|
|
1358
|
+
("lanczos2", False),
|
|
1359
|
+
("lanczos3", False),
|
|
1360
|
+
("gaussian", False),
|
|
1361
|
+
],
|
|
1362
|
+
)
|
|
1363
|
+
def test_drizzle_weights_squared(kernel, fc):
|
|
1364
|
+
n = 17
|
|
1365
|
+
in_shape = (n, n)
|
|
1366
|
+
|
|
1367
|
+
# input coordinate grid:
|
|
1368
|
+
y, x = np.indices(in_shape, dtype=np.float64)
|
|
1369
|
+
|
|
1370
|
+
in_sci1 = np.zeros(in_shape, dtype=np.float32)
|
|
1371
|
+
in_wht1 = np.zeros(in_shape, dtype=np.float32)
|
|
1372
|
+
in_sci1_sq = np.zeros(in_shape, dtype=np.float32)
|
|
1373
|
+
|
|
1374
|
+
in_sci2 = np.zeros(in_shape, dtype=np.float32)
|
|
1375
|
+
in_wht2 = np.zeros(in_shape, dtype=np.float32)
|
|
1376
|
+
in_sci2_sq = np.zeros(in_shape, dtype=np.float32)
|
|
1377
|
+
|
|
1378
|
+
xc = yc = n // 2
|
|
1379
|
+
|
|
1380
|
+
in_sci1[yc, xc] = 1.0
|
|
1381
|
+
in_wht1[yc, xc] = 0.99
|
|
1382
|
+
in_sci1_sq[yc, xc] = 0.5
|
|
1383
|
+
|
|
1384
|
+
in_sci2[yc, xc] = 7.0
|
|
1385
|
+
in_wht2[yc, xc] = 0.01
|
|
1386
|
+
in_sci2_sq[yc, xc] = 50.0
|
|
1387
|
+
|
|
1388
|
+
pixmap = np.dstack([x, y])
|
|
1389
|
+
|
|
1390
|
+
out_shape = (int(y.max()) + 1, int(x.max()) + 1)
|
|
1391
|
+
|
|
1392
|
+
if fc:
|
|
1393
|
+
# create a Drizzle object using all default parameters
|
|
1394
|
+
# (except for 'kernel', 'out_shape')
|
|
1395
|
+
driz = resample.Drizzle(
|
|
1396
|
+
kernel=kernel,
|
|
1397
|
+
out_shape=out_shape,
|
|
1398
|
+
fillval2=-99.0,
|
|
1399
|
+
)
|
|
1400
|
+
|
|
1401
|
+
assert driz.out_img is not None
|
|
1402
|
+
assert driz.out_img2 is None
|
|
1403
|
+
assert driz.total_exptime == 0.0
|
|
1404
|
+
|
|
1405
|
+
driz.add_image(
|
|
1406
|
+
data=in_sci1,
|
|
1407
|
+
exptime=1.0,
|
|
1408
|
+
pixmap=pixmap,
|
|
1409
|
+
weight_map=in_wht1,
|
|
1410
|
+
data2=[in_sci1_sq],
|
|
1411
|
+
)
|
|
1412
|
+
|
|
1413
|
+
driz.add_image(
|
|
1414
|
+
data=in_sci2,
|
|
1415
|
+
exptime=1.0,
|
|
1416
|
+
pixmap=pixmap,
|
|
1417
|
+
weight_map=in_wht2,
|
|
1418
|
+
data2=in_sci2_sq,
|
|
1419
|
+
)
|
|
1420
|
+
|
|
1421
|
+
assert isinstance(driz.out_img2, list)
|
|
1422
|
+
assert len(driz.out_img2) == 1
|
|
1423
|
+
|
|
1424
|
+
else:
|
|
1425
|
+
# create a Drizzle object using mostly default parameters
|
|
1426
|
+
driz = resample.Drizzle(
|
|
1427
|
+
kernel=kernel,
|
|
1428
|
+
out_img2=[None],
|
|
1429
|
+
fillval2=-99.0,
|
|
1430
|
+
)
|
|
1431
|
+
|
|
1432
|
+
assert driz.out_img is None
|
|
1433
|
+
assert driz.total_exptime == 0.0
|
|
1434
|
+
|
|
1435
|
+
with pytest.warns(Warning, match=f"Kernel '{kernel}' is not a flux-conserving kernel"):
|
|
1436
|
+
driz.add_image(
|
|
1437
|
+
data=in_sci1,
|
|
1438
|
+
exptime=1.0,
|
|
1439
|
+
pixmap=pixmap,
|
|
1440
|
+
weight_map=in_wht1,
|
|
1441
|
+
data2=[in_sci1_sq],
|
|
1442
|
+
)
|
|
1443
|
+
with pytest.warns(Warning, match=f"Kernel '{kernel}' is not a flux-conserving kernel"):
|
|
1444
|
+
driz.add_image(
|
|
1445
|
+
data=in_sci2,
|
|
1446
|
+
exptime=1.0,
|
|
1447
|
+
pixmap=pixmap,
|
|
1448
|
+
weight_map=in_wht2,
|
|
1449
|
+
data2=[in_sci2_sq],
|
|
1450
|
+
)
|
|
1451
|
+
|
|
1452
|
+
assert np.allclose(np.max(driz.out_img2), 0.495050013, rtol=1.0e-6, atol=0.0)
|
|
1453
|
+
|
|
1454
|
+
# check fill value
|
|
1455
|
+
assert np.allclose(np.min(driz.out_img2), -99.0, rtol=1.0e-6, atol=0.0)
|
|
1456
|
+
assert abs(float(driz.fillval2) + 99.0) < 1e-7
|
|
1457
|
+
|
|
1458
|
+
|
|
1459
|
+
@pytest.mark.filterwarnings("ignore:Kernel '")
|
|
1460
|
+
@pytest.mark.parametrize(
|
|
1461
|
+
"kernel_fc, pscale, weights",
|
|
1462
|
+
(
|
|
1463
|
+
x
|
|
1464
|
+
for x in product(
|
|
1465
|
+
[
|
|
1466
|
+
("square", True),
|
|
1467
|
+
("turbo", True),
|
|
1468
|
+
("point", True),
|
|
1469
|
+
("gaussian", False),
|
|
1470
|
+
# lanczos kernels do not support pscale != 1 or pixfrac != 1
|
|
1471
|
+
# ('lanczos2', False),
|
|
1472
|
+
# ('lanczos3', False),
|
|
1473
|
+
],
|
|
1474
|
+
[0.25, 0.5, 1, 1.2, 1.5],
|
|
1475
|
+
[(0.99, 0.01), (0.8, 0.2), (0.9, 1.5), (467, 733)],
|
|
1476
|
+
)
|
|
1477
|
+
),
|
|
1478
|
+
)
|
|
1479
|
+
def test_drizzle_weights_squared_pscale(kernel_fc, pscale, weights):
|
|
1480
|
+
n = 25
|
|
1481
|
+
shape = (n, n)
|
|
1482
|
+
|
|
1483
|
+
# unpack parameters:
|
|
1484
|
+
kernel, fc = kernel_fc
|
|
1485
|
+
|
|
1486
|
+
# pixel values in input data:
|
|
1487
|
+
dataval = [1.0, 7.0]
|
|
1488
|
+
|
|
1489
|
+
# pixel values in input variance:
|
|
1490
|
+
varval = [0.5, 50]
|
|
1491
|
+
|
|
1492
|
+
# input coordinate grid:
|
|
1493
|
+
y, x = np.indices(shape, dtype=np.float64)
|
|
1494
|
+
pixmap = np.dstack([x, y]) / pscale
|
|
1495
|
+
|
|
1496
|
+
data = [np.zeros(shape, dtype=np.float32) for _ in range(2)]
|
|
1497
|
+
weight = [np.zeros(shape, dtype=np.float32) for _ in range(2)]
|
|
1498
|
+
var = [np.zeros(shape, dtype=np.float32) for _ in range(2)]
|
|
1499
|
+
|
|
1500
|
+
xc = yc = n // 2
|
|
1501
|
+
sl = np.s_[yc - 4 : yc + 5, xc - 4 : xc + 5]
|
|
1502
|
+
for k in range(2):
|
|
1503
|
+
data[k][sl] = dataval[k]
|
|
1504
|
+
weight[k][sl] = weights[k]
|
|
1505
|
+
var[k][sl] = varval[k]
|
|
1506
|
+
|
|
1507
|
+
out_shape = (int(pixmap[..., 1].max()) + 1, int(pixmap[..., 0].max()) + 1)
|
|
1508
|
+
|
|
1509
|
+
# create a Drizzle object
|
|
1510
|
+
driz = resample.Drizzle(
|
|
1511
|
+
kernel=kernel,
|
|
1512
|
+
out_shape=out_shape,
|
|
1513
|
+
fillval=0.0,
|
|
1514
|
+
fillval2=0.0,
|
|
1515
|
+
)
|
|
1516
|
+
|
|
1517
|
+
# resample & add input images
|
|
1518
|
+
for k in range(2):
|
|
1519
|
+
driz.add_image(
|
|
1520
|
+
data=data[k],
|
|
1521
|
+
exptime=1.0,
|
|
1522
|
+
pixmap=pixmap,
|
|
1523
|
+
weight_map=weight[k],
|
|
1524
|
+
data2=var[k],
|
|
1525
|
+
)
|
|
1526
|
+
|
|
1527
|
+
mask = driz.out_ctx[0] > 0
|
|
1528
|
+
n_nonzero = np.sum(data[0] > 0.0)
|
|
1529
|
+
|
|
1530
|
+
rtol = 1.0e-6 if fc else 0.15
|
|
1531
|
+
|
|
1532
|
+
ideal_output = np.dot(dataval, weights) * n_nonzero
|
|
1533
|
+
ideal_output2 = np.dot(varval, np.square(weights)) / np.sum(weights) ** 2
|
|
1534
|
+
|
|
1535
|
+
tflux = np.sum(driz.out_img[mask] * driz.out_wht[mask])
|
|
1536
|
+
tflux2 = np.max(driz.out_img2[0])
|
|
1537
|
+
|
|
1538
|
+
# check output flux:
|
|
1539
|
+
assert np.allclose(tflux, ideal_output, rtol=rtol, atol=0.0)
|
|
1540
|
+
|
|
1541
|
+
# check output variance:
|
|
1542
|
+
# less restrictive (to account for pixel overlap variations):
|
|
1543
|
+
assert np.max(tflux2) <= ideal_output2 * (1 + rtol) and np.max(
|
|
1544
|
+
tflux2
|
|
1545
|
+
) >= 0.25 * ideal_output2 * (1 - rtol)
|
|
1546
|
+
|
|
1547
|
+
# more restrictive check assumes pixels have good overlaps:
|
|
1548
|
+
assert np.allclose(tflux2, ideal_output2, rtol=rtol, atol=0.0)
|
|
1549
|
+
|
|
1550
|
+
|
|
1551
|
+
def test_drizzle_weights_squared_bad_inputs():
|
|
1552
|
+
n = 21
|
|
1553
|
+
in_shape = (n, n)
|
|
1554
|
+
kernel = "square"
|
|
1555
|
+
|
|
1556
|
+
# input coordinate grid:
|
|
1557
|
+
y, x = np.indices(in_shape, dtype=np.float64)
|
|
1558
|
+
|
|
1559
|
+
in_sci1 = np.zeros(in_shape, dtype=np.float32)
|
|
1560
|
+
in_wht1 = np.zeros(in_shape, dtype=np.float32)
|
|
1561
|
+
in_sci1_sq = np.zeros(in_shape, dtype=np.float32)
|
|
1562
|
+
|
|
1563
|
+
in_sci2 = np.zeros(in_shape, dtype=np.float32)
|
|
1564
|
+
in_wht2 = np.zeros(in_shape, dtype=np.float32)
|
|
1565
|
+
in_sci2_sq = np.zeros(in_shape, dtype=np.float32)
|
|
1566
|
+
|
|
1567
|
+
pixmap = np.dstack([x, y])
|
|
1568
|
+
|
|
1569
|
+
out_shape = (int(y.max()) + 1, int(x.max()) + 1)
|
|
1570
|
+
|
|
1571
|
+
out_img = np.zeros(out_shape, dtype=np.float32)
|
|
1572
|
+
out_img2 = np.zeros(out_shape, dtype=np.float32)
|
|
1573
|
+
out_img2b = np.zeros(out_shape, dtype=np.float32)
|
|
1574
|
+
|
|
1575
|
+
# 1 - test same number of data2 is used each time:
|
|
1576
|
+
driz = resample.Drizzle(
|
|
1577
|
+
kernel=kernel,
|
|
1578
|
+
)
|
|
1579
|
+
|
|
1580
|
+
assert driz.out_img is None
|
|
1581
|
+
assert driz.out_img2 is None
|
|
1582
|
+
assert driz.total_exptime == 0.0
|
|
1583
|
+
|
|
1584
|
+
driz.add_image(
|
|
1585
|
+
data=in_sci1,
|
|
1586
|
+
exptime=1.0,
|
|
1587
|
+
pixmap=pixmap,
|
|
1588
|
+
weight_map=in_wht1,
|
|
1589
|
+
data2=None,
|
|
1590
|
+
)
|
|
1591
|
+
assert driz.out_img2 is None
|
|
1592
|
+
|
|
1593
|
+
with pytest.raises(ValueError) as err_info:
|
|
1594
|
+
driz.add_image(
|
|
1595
|
+
data=in_sci2,
|
|
1596
|
+
exptime=1.0,
|
|
1597
|
+
pixmap=pixmap,
|
|
1598
|
+
weight_map=in_wht2,
|
|
1599
|
+
data2=in_sci2_sq,
|
|
1600
|
+
)
|
|
1601
|
+
assert str(err_info.value).startswith("Mismatch between the number of 'out_img2' images")
|
|
1602
|
+
|
|
1603
|
+
# 2 - test same number of data2 is used each time:
|
|
1604
|
+
driz = resample.Drizzle(
|
|
1605
|
+
kernel=kernel,
|
|
1606
|
+
)
|
|
1607
|
+
|
|
1608
|
+
driz.add_image(
|
|
1609
|
+
data=in_sci1,
|
|
1610
|
+
exptime=1.0,
|
|
1611
|
+
pixmap=pixmap,
|
|
1612
|
+
weight_map=in_wht1,
|
|
1613
|
+
data2=in_sci1_sq,
|
|
1614
|
+
)
|
|
1615
|
+
|
|
1616
|
+
with pytest.raises(ValueError) as err_info:
|
|
1617
|
+
driz.add_image(
|
|
1618
|
+
data=in_sci2,
|
|
1619
|
+
exptime=1.0,
|
|
1620
|
+
pixmap=pixmap,
|
|
1621
|
+
weight_map=in_wht2,
|
|
1622
|
+
data2=None,
|
|
1623
|
+
)
|
|
1624
|
+
assert str(err_info.value).startswith("Mismatch between the number of 'out_img2' images")
|
|
1625
|
+
|
|
1626
|
+
# 3 - test same number of data2 is used each time:
|
|
1627
|
+
driz = resample.Drizzle(
|
|
1628
|
+
kernel=kernel,
|
|
1629
|
+
out_img2=[out_img2, out_img2b],
|
|
1630
|
+
)
|
|
1631
|
+
|
|
1632
|
+
with pytest.raises(ValueError) as err_info:
|
|
1633
|
+
driz.add_image(
|
|
1634
|
+
data=in_sci2,
|
|
1635
|
+
exptime=1.0,
|
|
1636
|
+
pixmap=pixmap,
|
|
1637
|
+
weight_map=in_wht2,
|
|
1638
|
+
data2=in_sci1_sq,
|
|
1639
|
+
)
|
|
1640
|
+
assert str(err_info.value).startswith("Mismatch between the number of 'out_img2' images")
|
|
1641
|
+
|
|
1642
|
+
# 4 - test same number of data2 is used each time:
|
|
1643
|
+
driz = resample.Drizzle(
|
|
1644
|
+
kernel=kernel,
|
|
1645
|
+
out_img=out_img,
|
|
1646
|
+
out_img2=out_img2,
|
|
1647
|
+
)
|
|
1648
|
+
|
|
1649
|
+
with pytest.raises(ValueError) as err_info:
|
|
1650
|
+
driz.add_image(
|
|
1651
|
+
data=in_sci2,
|
|
1652
|
+
exptime=1.0,
|
|
1653
|
+
pixmap=pixmap,
|
|
1654
|
+
weight_map=in_wht2,
|
|
1655
|
+
data2=None,
|
|
1656
|
+
)
|
|
1657
|
+
assert str(err_info.value).startswith("Mismatch between the number of 'out_img2' images")
|
|
1658
|
+
|
|
1659
|
+
# 5 - test mismatch between output data image and output variance image:
|
|
1660
|
+
out_img2 = np.zeros(tuple(s + 1 for s in out_shape), dtype=np.float32)
|
|
1661
|
+
|
|
1662
|
+
with pytest.raises(ValueError) as err_info:
|
|
1663
|
+
driz = resample.Drizzle(
|
|
1664
|
+
kernel=kernel,
|
|
1665
|
+
out_img=out_img,
|
|
1666
|
+
out_img2=out_img2,
|
|
1667
|
+
)
|
|
1668
|
+
assert str(err_info.value).startswith("Inconsistent data shapes specified:")
|
|
1669
|
+
|
|
1670
|
+
|
|
1671
|
+
def test_drizzle_weights_squared_array_shape_mismatch():
|
|
1672
|
+
n = 20
|
|
1673
|
+
in_shape = (n, n)
|
|
1674
|
+
in_shape1 = (n + 1, n + 1)
|
|
1675
|
+
kernel = "square"
|
|
1676
|
+
|
|
1677
|
+
# input coordinate grid:
|
|
1678
|
+
y, x = np.indices(in_shape, dtype=np.float64)
|
|
1679
|
+
|
|
1680
|
+
in_sci1 = np.zeros(in_shape, dtype=np.float32)
|
|
1681
|
+
in_sci1[n // 2, n // 2] = 2.222222222222
|
|
1682
|
+
in_sci1_sq = np.zeros(in_shape, dtype=np.float32)
|
|
1683
|
+
|
|
1684
|
+
in_wht2 = np.zeros(in_shape1, dtype=np.float32)
|
|
1685
|
+
in_sci2_sq = np.zeros(in_shape1, dtype=np.float32)
|
|
1686
|
+
|
|
1687
|
+
pixmap = np.dstack([x, y])
|
|
1688
|
+
|
|
1689
|
+
out_shape = (int(y.max()) + 1, int(x.max()) + 1)
|
|
1690
|
+
out_shape1 = (out_shape[0] + 1, out_shape[1] + 1)
|
|
1691
|
+
|
|
1692
|
+
out_img2 = np.zeros(out_shape, dtype=np.float32)
|
|
1693
|
+
out_img2b = np.zeros(out_shape1, dtype=np.float32)
|
|
1694
|
+
|
|
1695
|
+
with pytest.raises(ValueError) as err_info:
|
|
1696
|
+
driz = resample.Drizzle(
|
|
1697
|
+
kernel=kernel,
|
|
1698
|
+
out_img2=[out_img2, out_img2b],
|
|
1699
|
+
)
|
|
1700
|
+
assert str(err_info.value).startswith("Inconsistent data shapes specified:")
|
|
1701
|
+
|
|
1702
|
+
driz = resample.Drizzle(
|
|
1703
|
+
kernel=kernel,
|
|
1704
|
+
out_img=out_img2.copy(),
|
|
1705
|
+
out_img2=[out_img2, out_img2, None],
|
|
1706
|
+
)
|
|
1707
|
+
with pytest.raises(ValueError) as err_info:
|
|
1708
|
+
driz.add_image(
|
|
1709
|
+
data=in_sci1,
|
|
1710
|
+
exptime=1.0,
|
|
1711
|
+
pixmap=pixmap,
|
|
1712
|
+
weight_map=in_wht2,
|
|
1713
|
+
data2=[in_sci1_sq, in_sci2_sq, None],
|
|
1714
|
+
)
|
|
1715
|
+
assert str(err_info.value).startswith("'data2' shape(s) is not consistent with 'data' shape.")
|
|
1716
|
+
|
|
1717
|
+
driz = resample.Drizzle(
|
|
1718
|
+
kernel=kernel,
|
|
1719
|
+
out_img2=out_img2,
|
|
1720
|
+
)
|
|
1721
|
+
with pytest.raises(ValueError) as err_info:
|
|
1722
|
+
driz.add_image(
|
|
1723
|
+
data=in_sci1,
|
|
1724
|
+
exptime=1.0,
|
|
1725
|
+
pixmap=pixmap,
|
|
1726
|
+
weight_map=in_wht2,
|
|
1727
|
+
data2=in_sci2_sq,
|
|
1728
|
+
)
|
|
1729
|
+
assert str(err_info.value).startswith("'data2' shape is not consistent with 'data' shape.")
|
|
1730
|
+
|
|
1731
|
+
with pytest.raises(ValueError) as err_info:
|
|
1732
|
+
driz = resample.Drizzle(
|
|
1733
|
+
kernel=kernel,
|
|
1734
|
+
out_img2=[out_img2, out_img2b],
|
|
1735
|
+
)
|
|
1736
|
+
assert str(err_info.value).startswith("Inconsistent data shapes specified:")
|
|
1737
|
+
|
|
1738
|
+
# wrong weight shape
|
|
1739
|
+
driz = resample.Drizzle(
|
|
1740
|
+
kernel=kernel,
|
|
1741
|
+
)
|
|
1742
|
+
with pytest.raises(ValueError) as err_info:
|
|
1743
|
+
driz.add_image(
|
|
1744
|
+
data=in_sci1,
|
|
1745
|
+
exptime=1.0,
|
|
1746
|
+
pixmap=pixmap,
|
|
1747
|
+
weight_map=in_wht2,
|
|
1748
|
+
)
|
|
1749
|
+
assert str(err_info.value).startswith("'weight_map' shape is not consistent with 'data' shape.")
|
|
1750
|
+
|
|
1751
|
+
# zero-sized variance array
|
|
1752
|
+
driz = resample.Drizzle(
|
|
1753
|
+
kernel=kernel, out_img2=[out_img2, out_img2.copy(), out_img2.copy(), None]
|
|
1754
|
+
)
|
|
1755
|
+
driz.add_image(
|
|
1756
|
+
data=in_sci1, exptime=1.0, pixmap=pixmap, data2=[in_sci1, in_sci1, np.array([]), None]
|
|
1757
|
+
)
|
|
1758
|
+
driz.add_image(data=in_sci1, exptime=1.0, pixmap=pixmap, data2=[in_sci1, None, in_sci1, None])
|
|
1759
|
+
assert np.allclose(np.nansum(driz.out_img2[0]), 2.0 * np.nansum(driz.out_img2[1]))
|
|
1760
|
+
assert np.allclose(np.nansum(driz.out_img2[0]), 2.0 * np.nansum(driz.out_img2[2]))
|
|
1761
|
+
assert np.allclose(0.0, np.nansum(driz.out_img2[3]))
|
|
1762
|
+
|
|
1763
|
+
|
|
1764
|
+
@pytest.mark.parametrize(
|
|
1765
|
+
"kernel_fc, pscale_ratio, kscale_none",
|
|
1766
|
+
(
|
|
1767
|
+
x
|
|
1768
|
+
for x in product(
|
|
1769
|
+
[
|
|
1770
|
+
("square", True),
|
|
1771
|
+
("point", True),
|
|
1772
|
+
("turbo", True),
|
|
1773
|
+
("lanczos2", False),
|
|
1774
|
+
("lanczos3", False),
|
|
1775
|
+
("gaussian", False),
|
|
1776
|
+
],
|
|
1777
|
+
[0.9, 1.0, 1.2],
|
|
1778
|
+
[False, True],
|
|
1779
|
+
)
|
|
1780
|
+
),
|
|
1781
|
+
)
|
|
1782
|
+
def test_drizzle_var_identical_to_nonvar(kernel_fc, pscale_ratio, kscale_none):
|
|
1783
|
+
"""Test that the resampled science image using code with support for
|
|
1784
|
+
variance-propagation is identical to the resampled science image
|
|
1785
|
+
using code without support for variance-propagation (original code).
|
|
1786
|
+
"""
|
|
1787
|
+
kernel, fc = kernel_fc
|
|
1788
|
+
|
|
1789
|
+
if kscale_none:
|
|
1790
|
+
kscale = None
|
|
1791
|
+
else:
|
|
1792
|
+
kscale = pscale_ratio
|
|
1793
|
+
|
|
1794
|
+
amplitude = 100.0
|
|
1795
|
+
inwcs = wcs_from_file("j8bt06nyq_flt.fits", ext=1)
|
|
1796
|
+
insci = amplitude * np.random.random(inwcs.array_shape).astype(np.float32)
|
|
1797
|
+
inwht = np.ones_like(insci)
|
|
1798
|
+
output_wcs, _ = wcs_from_file("reference_square_image.fits", ext=1, return_data=True)
|
|
1799
|
+
|
|
1800
|
+
pixmap = utils.calc_pixmap(
|
|
1801
|
+
inwcs,
|
|
1802
|
+
output_wcs,
|
|
1803
|
+
)
|
|
1804
|
+
|
|
1805
|
+
driz1 = resample.Drizzle(
|
|
1806
|
+
kernel=kernel,
|
|
1807
|
+
fillval="NaN",
|
|
1808
|
+
out_shape=output_wcs.array_shape,
|
|
1809
|
+
exptime=0.0,
|
|
1810
|
+
begin_ctx_id=0,
|
|
1811
|
+
max_ctx_id=1,
|
|
1812
|
+
disable_ctx=False,
|
|
1813
|
+
)
|
|
1814
|
+
|
|
1815
|
+
driz2 = resample.Drizzle(
|
|
1816
|
+
kernel=kernel,
|
|
1817
|
+
fillval="NaN",
|
|
1818
|
+
out_shape=output_wcs.array_shape,
|
|
1819
|
+
exptime=0.0,
|
|
1820
|
+
begin_ctx_id=0,
|
|
1821
|
+
max_ctx_id=1,
|
|
1822
|
+
disable_ctx=False,
|
|
1823
|
+
)
|
|
1824
|
+
|
|
1825
|
+
if fc:
|
|
1826
|
+
driz1.add_image(
|
|
1827
|
+
insci,
|
|
1828
|
+
exptime=13.0,
|
|
1829
|
+
pixmap=pixmap,
|
|
1830
|
+
weight_map=inwht,
|
|
1831
|
+
iscale=pscale_ratio**2,
|
|
1832
|
+
pixel_scale_ratio=kscale,
|
|
1833
|
+
xmin=10,
|
|
1834
|
+
ymin=10,
|
|
1835
|
+
xmax=output_wcs.array_shape[0] - 10,
|
|
1836
|
+
ymax=output_wcs.array_shape[1] - 10,
|
|
1837
|
+
)
|
|
1838
|
+
driz2.add_image(
|
|
1839
|
+
insci,
|
|
1840
|
+
data2=insci,
|
|
1841
|
+
exptime=13.0,
|
|
1842
|
+
pixmap=pixmap,
|
|
1843
|
+
weight_map=inwht,
|
|
1844
|
+
iscale=pscale_ratio**2,
|
|
1845
|
+
pixel_scale_ratio=kscale,
|
|
1846
|
+
xmin=10,
|
|
1847
|
+
ymin=10,
|
|
1848
|
+
xmax=output_wcs.array_shape[0] - 10,
|
|
1849
|
+
ymax=output_wcs.array_shape[1] - 10,
|
|
1850
|
+
)
|
|
1851
|
+
else:
|
|
1852
|
+
with pytest.warns(Warning, match=f"Kernel '{kernel}' is not a flux-conserving kernel"):
|
|
1853
|
+
driz1.add_image(
|
|
1854
|
+
insci,
|
|
1855
|
+
exptime=13.0,
|
|
1856
|
+
pixmap=pixmap,
|
|
1857
|
+
weight_map=inwht,
|
|
1858
|
+
iscale=pscale_ratio**2,
|
|
1859
|
+
pixel_scale_ratio=kscale,
|
|
1860
|
+
xmin=10,
|
|
1861
|
+
ymin=10,
|
|
1862
|
+
xmax=output_wcs.array_shape[0] - 10,
|
|
1863
|
+
ymax=output_wcs.array_shape[1] - 10,
|
|
1864
|
+
)
|
|
1865
|
+
|
|
1866
|
+
with pytest.warns(Warning, match=f"Kernel '{kernel}' is not a flux-conserving kernel"):
|
|
1867
|
+
driz2.add_image(
|
|
1868
|
+
insci,
|
|
1869
|
+
data2=insci,
|
|
1870
|
+
exptime=13.0,
|
|
1871
|
+
pixmap=pixmap,
|
|
1872
|
+
weight_map=inwht,
|
|
1873
|
+
iscale=pscale_ratio**2,
|
|
1874
|
+
pixel_scale_ratio=kscale,
|
|
1875
|
+
xmin=10,
|
|
1876
|
+
ymin=10,
|
|
1877
|
+
xmax=output_wcs.array_shape[0] - 10,
|
|
1878
|
+
ymax=output_wcs.array_shape[1] - 10,
|
|
1879
|
+
)
|
|
1880
|
+
|
|
1881
|
+
assert np.allclose(
|
|
1882
|
+
driz1.out_img,
|
|
1883
|
+
driz2.out_img,
|
|
1884
|
+
rtol=0.0,
|
|
1885
|
+
atol=5.0 * amplitude * np.finfo(np.float32).eps,
|
|
1886
|
+
equal_nan=True,
|
|
1887
|
+
), "Resampled science images are not identical."
|
|
1888
|
+
|
|
1889
|
+
assert np.allclose(
|
|
1890
|
+
driz1.out_wht,
|
|
1891
|
+
driz2.out_wht,
|
|
1892
|
+
rtol=0.0,
|
|
1893
|
+
atol=5.0 * amplitude * np.finfo(np.float32).eps,
|
|
1894
|
+
equal_nan=True,
|
|
1895
|
+
), "Resampled weight images are not identical."
|
|
1896
|
+
|
|
1897
|
+
assert np.all(driz1.out_ctx == driz2.out_ctx), "Context images are not identical."
|
|
1898
|
+
|
|
1899
|
+
|
|
1900
|
+
@pytest.mark.parametrize("create_out_dq", [True, False])
|
|
1901
|
+
@pytest.mark.parametrize("shift", [0.0, 0.5])
|
|
1902
|
+
@pytest.mark.parametrize("add_non_dq_image", [True, False])
|
|
1903
|
+
def test_drizzle_dq_propagation(create_out_dq, shift, add_non_dq_image):
|
|
1904
|
+
n = 200
|
|
1905
|
+
in_shape = (n, n)
|
|
1906
|
+
|
|
1907
|
+
# input coordinate grid:
|
|
1908
|
+
y, x = np.indices(in_shape, dtype=np.float64) + shift
|
|
1909
|
+
|
|
1910
|
+
# simulate data:
|
|
1911
|
+
in_sci = np.ones(in_shape, dtype=np.float32)
|
|
1912
|
+
in_wht = np.ones(in_shape, dtype=np.float32)
|
|
1913
|
+
# use int16 to test up to 16 bits and signed integers
|
|
1914
|
+
in_dq1 = np.zeros(in_shape, dtype=np.int16)
|
|
1915
|
+
# use uint32 to test up to 32 bits and unsigned integers (default type)
|
|
1916
|
+
in_dq2 = np.zeros(in_shape, dtype=np.uint32)
|
|
1917
|
+
if create_out_dq:
|
|
1918
|
+
out_dq = np.zeros(in_shape, dtype=np.uint32)
|
|
1919
|
+
else:
|
|
1920
|
+
out_dq = None
|
|
1921
|
+
|
|
1922
|
+
xyc = n // 2
|
|
1923
|
+
in_dq1[xyc, xyc] = 1 << 0
|
|
1924
|
+
in_dq1[xyc + 1, xyc] = 1 << 1
|
|
1925
|
+
in_dq1[xyc, xyc + 1] = 1 << 2
|
|
1926
|
+
in_dq1[xyc + 1, xyc + 1] = 1 << 3
|
|
1927
|
+
|
|
1928
|
+
in_dq2[xyc, xyc] = 1 << 4
|
|
1929
|
+
in_dq2[xyc + 1, xyc] = 1 << 5
|
|
1930
|
+
in_dq2[xyc, xyc + 1] = 1 << 6
|
|
1931
|
+
in_dq2[xyc + 1, xyc + 1] = 1 << 7
|
|
1932
|
+
|
|
1933
|
+
driz = resample.Drizzle(
|
|
1934
|
+
kernel="square",
|
|
1935
|
+
out_dq=out_dq,
|
|
1936
|
+
)
|
|
1937
|
+
|
|
1938
|
+
pixmap = np.dstack([x, y])
|
|
1939
|
+
|
|
1940
|
+
driz.add_image(
|
|
1941
|
+
in_sci,
|
|
1942
|
+
dq=in_dq1,
|
|
1943
|
+
exptime=1.0,
|
|
1944
|
+
pixmap=pixmap,
|
|
1945
|
+
weight_map=in_wht,
|
|
1946
|
+
)
|
|
1947
|
+
|
|
1948
|
+
if shift == 0.0:
|
|
1949
|
+
assert driz.out_dq[xyc, xyc] == in_dq1[xyc, xyc]
|
|
1950
|
+
assert driz.out_dq[xyc + 1, xyc] == in_dq1[xyc + 1, xyc]
|
|
1951
|
+
assert driz.out_dq[xyc, xyc + 1] == in_dq1[xyc, xyc + 1]
|
|
1952
|
+
assert driz.out_dq[xyc + 1, xyc + 1] == in_dq1[xyc + 1, xyc + 1]
|
|
1953
|
+
else:
|
|
1954
|
+
# with shift=0.5 all 4 input pixels should contribute to the output
|
|
1955
|
+
# pixel at (xyc+1, xyc+1)
|
|
1956
|
+
assert driz.out_dq[xyc + 1, xyc + 1] == sum(1 << i for i in range(4))
|
|
1957
|
+
|
|
1958
|
+
if add_non_dq_image:
|
|
1959
|
+
driz.add_image(
|
|
1960
|
+
in_sci,
|
|
1961
|
+
exptime=1.0,
|
|
1962
|
+
pixmap=pixmap,
|
|
1963
|
+
weight_map=in_wht,
|
|
1964
|
+
)
|
|
1965
|
+
|
|
1966
|
+
if shift == 0.0:
|
|
1967
|
+
assert driz.out_dq[xyc, xyc] == in_dq1[xyc, xyc]
|
|
1968
|
+
assert driz.out_dq[xyc + 1, xyc] == in_dq1[xyc + 1, xyc]
|
|
1969
|
+
assert driz.out_dq[xyc, xyc + 1] == in_dq1[xyc, xyc + 1]
|
|
1970
|
+
assert driz.out_dq[xyc + 1, xyc + 1] == in_dq1[xyc + 1, xyc + 1]
|
|
1971
|
+
else:
|
|
1972
|
+
# with shift=0.5 all 4 input pixels should contribute to the output
|
|
1973
|
+
# pixel at (xyc+1, xyc+1)
|
|
1974
|
+
assert driz.out_dq[xyc + 1, xyc + 1] == sum(1 << i for i in range(4))
|
|
1975
|
+
|
|
1976
|
+
driz.add_image(
|
|
1977
|
+
in_sci,
|
|
1978
|
+
dq=in_dq2,
|
|
1979
|
+
exptime=1.0,
|
|
1980
|
+
pixmap=pixmap,
|
|
1981
|
+
weight_map=in_wht,
|
|
1982
|
+
)
|
|
1983
|
+
|
|
1984
|
+
in_dq_total = in_dq1 + in_dq2
|
|
1985
|
+
|
|
1986
|
+
if shift == 0.0:
|
|
1987
|
+
assert driz.out_dq[xyc, xyc] == in_dq_total[xyc, xyc]
|
|
1988
|
+
assert driz.out_dq[xyc + 1, xyc] == in_dq_total[xyc + 1, xyc]
|
|
1989
|
+
assert driz.out_dq[xyc, xyc + 1] == in_dq_total[xyc, xyc + 1]
|
|
1990
|
+
assert driz.out_dq[xyc + 1, xyc + 1] == in_dq_total[xyc + 1, xyc + 1]
|
|
1991
|
+
else:
|
|
1992
|
+
# with shift=0.5 all 4 input pixels should contribute to the output
|
|
1993
|
+
# pixel at (xyc+1, xyc+1)
|
|
1994
|
+
assert driz.out_dq[xyc + 1, xyc + 1] == sum(1 << i for i in range(8))
|
|
1995
|
+
|
|
1996
|
+
if add_non_dq_image:
|
|
1997
|
+
driz.add_image(
|
|
1998
|
+
in_sci,
|
|
1999
|
+
exptime=1.0,
|
|
2000
|
+
pixmap=pixmap,
|
|
2001
|
+
weight_map=in_wht,
|
|
2002
|
+
)
|
|
2003
|
+
|
|
2004
|
+
if shift == 0.0:
|
|
2005
|
+
assert driz.out_dq[xyc, xyc] == in_dq_total[xyc, xyc]
|
|
2006
|
+
assert driz.out_dq[xyc + 1, xyc] == in_dq_total[xyc + 1, xyc]
|
|
2007
|
+
assert driz.out_dq[xyc, xyc + 1] == in_dq_total[xyc, xyc + 1]
|
|
2008
|
+
assert driz.out_dq[xyc + 1, xyc + 1] == in_dq_total[xyc + 1, xyc + 1]
|
|
2009
|
+
else:
|
|
2010
|
+
# with shift=0.5 all 4 input pixels should contribute to the output
|
|
2011
|
+
# pixel at (xyc+1, xyc+1)
|
|
2012
|
+
assert driz.out_dq[xyc + 1, xyc + 1] == sum(1 << i for i in range(8))
|
|
2013
|
+
|
|
2014
|
+
|
|
2015
|
+
def test_drizzle_dq_propagation_wrong_shape():
|
|
2016
|
+
n = 200
|
|
2017
|
+
in_shape = (n, n)
|
|
2018
|
+
|
|
2019
|
+
# input coordinate grid:
|
|
2020
|
+
y, x = np.indices(in_shape, dtype=np.float64)
|
|
2021
|
+
|
|
2022
|
+
# simulate data:
|
|
2023
|
+
in_sci = np.ones(in_shape, dtype=np.float32)
|
|
2024
|
+
in_wht = np.ones(in_shape, dtype=np.float32)
|
|
2025
|
+
in_dq = np.zeros(tuple(i + 1 for i in in_shape), dtype=np.uint32)
|
|
2026
|
+
out_img = np.zeros(in_shape, dtype=np.float32)
|
|
2027
|
+
out_dq = np.zeros(tuple(i + 1 for i in in_shape), dtype=np.uint32)
|
|
2028
|
+
|
|
2029
|
+
with pytest.raises(ValueError) as err_info:
|
|
2030
|
+
driz = resample.Drizzle(
|
|
2031
|
+
kernel="square",
|
|
2032
|
+
out_img=out_img,
|
|
2033
|
+
out_dq=out_dq,
|
|
2034
|
+
)
|
|
2035
|
+
assert str(err_info.value).startswith("Inconsistent data shapes specified:")
|
|
2036
|
+
|
|
2037
|
+
driz = resample.Drizzle(
|
|
2038
|
+
kernel="square",
|
|
2039
|
+
)
|
|
2040
|
+
|
|
2041
|
+
pixmap = np.dstack([x, y])
|
|
2042
|
+
|
|
2043
|
+
with pytest.raises(ValueError) as err_info:
|
|
2044
|
+
driz.add_image(
|
|
2045
|
+
in_sci,
|
|
2046
|
+
dq=in_dq,
|
|
2047
|
+
exptime=1.0,
|
|
2048
|
+
pixmap=pixmap,
|
|
2049
|
+
weight_map=in_wht,
|
|
2050
|
+
)
|
|
2051
|
+
assert str(err_info.value).startswith("'dq' shape is not consistent with 'data' shape.")
|
|
2052
|
+
|
|
2053
|
+
|
|
2054
|
+
def test_drizzle_dq_propagation_wrong_type():
|
|
2055
|
+
n = 20
|
|
2056
|
+
in_shape = (n, n)
|
|
2057
|
+
|
|
2058
|
+
# input coordinate grid:
|
|
2059
|
+
y, x = np.indices(in_shape, dtype=np.float64)
|
|
2060
|
+
|
|
2061
|
+
# simulate data:
|
|
2062
|
+
in_sci = np.ones(in_shape, dtype=np.float32)
|
|
2063
|
+
in_wht = np.ones(in_shape, dtype=np.float32)
|
|
2064
|
+
in_dq = np.zeros(in_shape, dtype=np.uint64)
|
|
2065
|
+
out_img = np.zeros(in_shape, dtype=np.float32)
|
|
2066
|
+
out_dq = np.zeros(in_shape, dtype=np.uint64)
|
|
2067
|
+
|
|
2068
|
+
with pytest.raises(TypeError) as err_info:
|
|
2069
|
+
driz = resample.Drizzle(
|
|
2070
|
+
kernel="square",
|
|
2071
|
+
out_img=out_img,
|
|
2072
|
+
out_dq=out_dq,
|
|
2073
|
+
)
|
|
2074
|
+
assert str(err_info.value).startswith(
|
|
2075
|
+
"'out_dq' must be of an unsigned integer type with itemsize of 4 bytes or less"
|
|
2076
|
+
)
|
|
2077
|
+
|
|
2078
|
+
driz = resample.Drizzle(
|
|
2079
|
+
kernel="square",
|
|
2080
|
+
)
|
|
2081
|
+
|
|
2082
|
+
pixmap = np.dstack([x, y])
|
|
2083
|
+
|
|
2084
|
+
with pytest.raises(TypeError) as err_info:
|
|
2085
|
+
driz.add_image(
|
|
2086
|
+
in_sci,
|
|
2087
|
+
dq=in_dq,
|
|
2088
|
+
exptime=1.0,
|
|
2089
|
+
pixmap=pixmap,
|
|
2090
|
+
weight_map=in_wht,
|
|
2091
|
+
)
|
|
2092
|
+
assert str(err_info.value).startswith(
|
|
2093
|
+
"'dq' must be of an unsigned integer type with itemsize of 4 bytes or less."
|
|
2094
|
+
)
|
|
2095
|
+
|
|
2096
|
+
|
|
2097
|
+
@pytest.mark.parametrize(
|
|
2098
|
+
"kernel, pscale_ratio, use_var",
|
|
2099
|
+
(
|
|
2100
|
+
x
|
|
2101
|
+
for x in product(
|
|
2102
|
+
[
|
|
2103
|
+
"square",
|
|
2104
|
+
"point",
|
|
2105
|
+
"turbo",
|
|
2106
|
+
"lanczos2",
|
|
2107
|
+
"lanczos3",
|
|
2108
|
+
"gaussian",
|
|
2109
|
+
],
|
|
2110
|
+
[0.9, 1.2, 0.3],
|
|
2111
|
+
[True, False],
|
|
2112
|
+
)
|
|
2113
|
+
),
|
|
2114
|
+
)
|
|
2115
|
+
@pytest.mark.filterwarnings(r"ignore:Argument 'scale' has been deprecated.*:DeprecationWarning")
|
|
2116
|
+
@pytest.mark.filterwarnings(r"ignore:Kernel '.*' is not a flux-conserving kernel:Warning")
|
|
2117
|
+
def test_drizzle_ipscale_same_as_scale(kernel, pscale_ratio, use_var):
|
|
2118
|
+
"""Test that the resampled science image using new "pixel_scale_ratio" and
|
|
2119
|
+
"iscale" parameters is identical to the resampled science image
|
|
2120
|
+
using the old "scale" parameter.
|
|
2121
|
+
|
|
2122
|
+
TODO: remove this test when support for "scale" is removed.
|
|
2123
|
+
"""
|
|
2124
|
+
amplitude = 100.0
|
|
2125
|
+
inwcs = wcs_from_file("j8bt06nyq_flt.fits", ext=1)
|
|
2126
|
+
insci = amplitude * np.random.random(inwcs.array_shape).astype(np.float32)
|
|
2127
|
+
inwht = np.ones_like(insci)
|
|
2128
|
+
output_wcs, _ = wcs_from_file("reference_square_image.fits", ext=1, return_data=True)
|
|
2129
|
+
|
|
2130
|
+
pixmap = utils.calc_pixmap(
|
|
2131
|
+
inwcs,
|
|
2132
|
+
output_wcs,
|
|
2133
|
+
)
|
|
2134
|
+
|
|
2135
|
+
driz1 = resample.Drizzle(
|
|
2136
|
+
kernel=kernel,
|
|
2137
|
+
fillval="NaN",
|
|
2138
|
+
out_shape=output_wcs.array_shape,
|
|
2139
|
+
exptime=0.0,
|
|
2140
|
+
begin_ctx_id=0,
|
|
2141
|
+
max_ctx_id=1,
|
|
2142
|
+
disable_ctx=False,
|
|
2143
|
+
)
|
|
2144
|
+
|
|
2145
|
+
driz2 = resample.Drizzle(
|
|
2146
|
+
kernel=kernel,
|
|
2147
|
+
fillval="NaN",
|
|
2148
|
+
out_shape=output_wcs.array_shape,
|
|
2149
|
+
exptime=0.0,
|
|
2150
|
+
begin_ctx_id=0,
|
|
2151
|
+
max_ctx_id=1,
|
|
2152
|
+
disable_ctx=False,
|
|
2153
|
+
)
|
|
2154
|
+
|
|
2155
|
+
driz1.add_image(
|
|
2156
|
+
insci,
|
|
2157
|
+
exptime=13.0,
|
|
2158
|
+
pixmap=pixmap,
|
|
2159
|
+
weight_map=inwht,
|
|
2160
|
+
iscale=pscale_ratio**2,
|
|
2161
|
+
pixel_scale_ratio=pscale_ratio,
|
|
2162
|
+
xmin=10,
|
|
2163
|
+
ymin=10,
|
|
2164
|
+
xmax=output_wcs.array_shape[0] - 10,
|
|
2165
|
+
ymax=output_wcs.array_shape[1] - 10,
|
|
2166
|
+
)
|
|
2167
|
+
|
|
2168
|
+
if use_var:
|
|
2169
|
+
kwargs = {"data2": insci}
|
|
2170
|
+
else:
|
|
2171
|
+
kwargs = {}
|
|
2172
|
+
|
|
2173
|
+
driz2.add_image(
|
|
2174
|
+
insci,
|
|
2175
|
+
exptime=13.0,
|
|
2176
|
+
pixmap=pixmap,
|
|
2177
|
+
weight_map=inwht,
|
|
2178
|
+
scale=pscale_ratio,
|
|
2179
|
+
xmin=10,
|
|
2180
|
+
ymin=10,
|
|
2181
|
+
xmax=output_wcs.array_shape[0] - 10,
|
|
2182
|
+
ymax=output_wcs.array_shape[1] - 10,
|
|
2183
|
+
**kwargs,
|
|
2184
|
+
)
|
|
2185
|
+
|
|
2186
|
+
assert np.allclose(
|
|
2187
|
+
driz1.out_img,
|
|
2188
|
+
driz2.out_img,
|
|
2189
|
+
rtol=0.0,
|
|
2190
|
+
atol=5.0 * amplitude * np.finfo(np.float32).eps,
|
|
2191
|
+
equal_nan=True,
|
|
2192
|
+
), "Resampled science images are not identical."
|
|
2193
|
+
|
|
2194
|
+
assert np.allclose(
|
|
2195
|
+
driz1.out_wht,
|
|
2196
|
+
driz2.out_wht,
|
|
2197
|
+
rtol=0.0,
|
|
2198
|
+
atol=5.0 * amplitude * np.finfo(np.float32).eps,
|
|
2199
|
+
equal_nan=True,
|
|
2200
|
+
), "Resampled weight images are not identical."
|
|
2201
|
+
|
|
2202
|
+
assert np.all(driz1.out_ctx == driz2.out_ctx), "Context images are not identical."
|