@@ -5,18 +5,20 @@ index 4f4c623a..c1ba101f 100644
5
5
@@ -12,5 +12,5 @@
6
6
# this list of conditions and the disclaimer given in the documentation
7
7
# and/or other materials provided with the distribution.
8
-
8
+
9
9
- __version__ = '1.3.3'
10
10
+ __version__ = '1.3.3.1'
11
11
__version_info__ = tuple(map(int, __version__.split('.')))
12
- diff --git a/piff/psf.py b/piff/psf.py
13
- index f1b526d5..d1a2d9ef 100644
14
- --- a/piff/psf.py
15
- +++ b/piff/psf.py
16
- @@ -24,6 +24,24 @@
17
- from .star import Star, StarData
18
- from .util import write_kwargs, read_kwargs
19
-
12
+ diff --git a/piff/pixelgrid.py b/piff/pixelgrid.py
13
+ index 4d113c77..0c10bb8c 100644
14
+ --- a/piff/pixelgrid.py
15
+ +++ b/piff/pixelgrid.py
16
+ @@ -25,6 +25,26 @@
17
+ from .model import Model
18
+ from .star import Star, StarData, StarFit
19
+
20
+ + APODIZE_PARAMS = (1.0 * 0.263, 4.25 * 0.263)
21
+ +
20
22
+
21
23
+ def _ap_kern_kern(x, m, h):
22
24
+ # cumulative triweight kernel
@@ -35,68 +37,31 @@ index f1b526d5..d1a2d9ef 100644
35
37
+ return apval
36
38
+
37
39
+
38
- class PSF(object):
39
- """The base class for describing a PSF model across a field of view.
40
-
41
- @@ -99,7 +117,7 @@ def parseKwargs(cls, config_psf, logger=None):
42
- raise NotImplementedError("Derived classes must define the parseKwargs function")
43
-
44
- def draw(self, x, y, chipnum=None, flux=1.0, center=None, offset=None, stamp_size=48,
45
- - image=None, logger=None, **kwargs):
46
- + image=None, logger=None, apodize=(1.0, 4.25), **kwargs):
47
- r"""Draws an image of the PSF at a given location.
48
-
49
- The normal usage would be to specify (chipnum, x, y), in which case Piff will use the
50
- @@ -161,6 +179,11 @@ def draw(self, x, y, chipnum=None, flux=1.0, center=None, offset=None, stamp_siz
51
- [default: 48]
52
- :param image: An existing image on which to draw, if desired. [default: None]
53
- :param logger: A logger object for logging debug info. [default: None]
54
- + :param apodize: Optional parameter to set apodizatoon. If a float/int, gives the
55
- + number of half light radii after which the profile is smoothy apodized
56
- + to zero a width of ~2.55 half light radii. If a tuple/list, gives
57
- + the apodization width and the apodization radius in pixels.
58
- + [default: (1.0, 4.25), which means a width of 1 pixel and radius of 4.25 pixels.]
59
- :param \**kwargs: Any additional properties required for the interpolation.
60
-
61
- :returns: A GalSim Image of the PSF
62
- @@ -201,6 +224,37 @@ def draw(self, x, y, chipnum=None, flux=1.0, center=None, offset=None, stamp_siz
63
-
64
- prof.drawImage(image, method=method, center=center)
65
-
66
- + if apodize:
67
- + xpix, ypix = image.get_pixel_centers()
68
- + dx = xpix - center[0]
69
- + dy = ypix - center[1]
40
+ class PixelGrid(Model):
41
+ """A PSF modeled as interpolation between a grid of points.
42
+
43
+ @@ -445,6 +465,21 @@ def getProfile(self, params):
44
+ :returns: a galsim.GSObject instance
45
+ """
46
+ im = galsim.Image(params.reshape(self.size,self.size), scale=self.scale)
47
+ +
48
+ + if APODIZE_PARAMS is not None:
49
+ + xpix, ypix = im.get_pixel_centers()
50
+ + # use_true_center = False below
51
+ + dx = xpix - im.center.x
52
+ + dy = ypix - im.center.y
70
53
+ r2 = dx**2 + dy**2
71
54
+
72
- + if isinstance(apodize, (tuple, list)):
73
- + apwidth, aprad = apodize
74
- + else:
75
- + wcs = image.wcs
76
- + try:
77
- + image.wcs = None
78
- + image.scale = 1.0
79
- + hlr = image.calculateHLR(center=galsim.PositionD(center))
80
- + finally:
81
- + image.wcs = wcs
82
- + aprad = apodize * hlr
83
- + msk_nonzero = image.array != 0
84
- + max_r = min(
85
- + np.abs(dx[(dx < 0) & msk_nonzero].min()),
86
- + np.abs(dx[(dx > 0) & msk_nonzero].max()),
87
- + np.abs(dy[(dy < 0) & msk_nonzero].min()),
88
- + np.abs(dy[(dy > 0) & msk_nonzero].max()),
89
- + )
90
- + apwidth = np.abs(hlr) / 2.355
91
- + apwidth = min(max(apwidth, 0.5), 5.0)
92
- + aprad = max(min(aprad, max_r - 6 * apwidth - 1), 2 * apwidth)
55
+ + apwidth, aprad = APODIZE_PARAMS # in arcsec
56
+ + _apwidth = apwidth / self.scale # convert to pixels
57
+ + _aprad = aprad / self.scale # convert to pixels
93
58
+
94
- + apim = image ._array * _ap_kern_kern(aprad , np.sqrt(r2), apwidth )
95
- + image ._array = apim / np.sum(apim) * np.sum(image ._array)
59
+ + apim = im ._array * _ap_kern_kern(_aprad , np.sqrt(r2), _apwidth )
60
+ + im ._array = apim / np.sum(apim) * np.sum(im ._array)
96
61
+
97
- return image
98
-
99
- def get_profile(self, x, y, chipnum=None, flux=1.0, logger=None, **kwargs):
62
+ return galsim.InterpolatedImage(im, x_interpolant=self.interp,
63
+ use_true_center=False, flux=1.)
64
+
100
65
diff --git a/requirements.txt b/requirements.txt
101
66
index d03b7b19..cfd6693b 100644
102
67
--- a/requirements.txt
@@ -114,171 +79,13 @@ index d03b7b19..cfd6693b 100644
114
79
+ galsim>=2.3,<2.5
115
80
treegp>=0.6
116
81
threadpoolctl>=3.1
117
- diff --git a/tests/test_pixel.py b/tests/test_pixel.py
118
- index 2cd33e73..d0b4fc22 100644
119
- --- a/tests/test_pixel.py
120
- +++ b/tests/test_pixel.py
121
- @@ -1531,7 +1531,7 @@ def test_color():
122
- # Check the convenience function that an end user would typically use
123
- offset = s.center_to_offset(s.fit.center)
124
- image = psf.draw(x=s['x'], y=s['y'], color=s['color'],
125
- - stamp_size=32, flux=s.fit.flux, offset=offset)
126
- + stamp_size=32, flux=s.fit.flux, offset=offset, apodize=None)
127
- # They may be up to 1 pixel off in either direction, so find the common bounds.
128
- b = image.bounds & fit_stamp.bounds
129
- np.testing.assert_allclose(image[b].array, fit_stamp[b].array, rtol=1.e-6, atol=1.e-4)
130
- diff --git a/tests/test_simple.py b/tests/test_simple.py
131
- index 57e23910..51c85be8 100644
132
- --- a/tests/test_simple.py
133
- +++ b/tests/test_simple.py
134
- @@ -309,12 +309,12 @@ def test_single_image():
135
-
136
- # test that draw works
137
- test_image = psf.draw(x=target['x'], y=target['y'], stamp_size=config['input']['stamp_size'],
138
- - flux=target.fit.flux, offset=target.fit.center)
139
- + flux=target.fit.flux, offset=target.fit.center, apodize=None)
140
- # this image should be the same values as test_star
141
- assert test_image == test_star.image
142
- # test that draw does not copy the image
143
- image_ref = psf.draw(x=target['x'], y=target['y'], stamp_size=config['input']['stamp_size'],
144
- - flux=target.fit.flux, offset=target.fit.center, image=test_image)
145
- + flux=target.fit.flux, offset=target.fit.center, image=test_image, apodize=None)
146
- image_ref.array[0,0] = 123456789
147
- assert test_image.array[0,0] == image_ref.array[0,0]
148
- assert test_star.image.array[0,0] != test_image.array[0,0]
149
- @@ -743,7 +743,7 @@ def test_draw():
150
-
151
- # Now use the regular PSF.draw() command. This version is equivalent to the above.
152
- # (It's not equal all the way to machine precision, but pretty close.)
153
- - im1 = psf.draw(x, y, chipnum, stamp_size=48)
154
- + im1 = psf.draw(x, y, chipnum, stamp_size=48, apodize=None)
155
- np.testing.assert_allclose(im1.array, star.data.image.array, rtol=1.e-14, atol=1.e-14)
156
-
157
- # The wcs in the image is the wcs of the original image
158
- @@ -768,13 +768,13 @@ def test_draw():
159
- # We can center the star at an arbitrary location on the image.
160
- # The default is equivalent to center=(x,y). So check that this is equivalent.
161
- # Also, 48 is the default stamp size, so that can be omitted here.
162
- - im2 = psf.draw(x, y, chipnum, center=(x,y))
163
- + im2 = psf.draw(x, y, chipnum, center=(x,y), apodize=None)
164
- assert im2.bounds == im1.bounds
165
- np.testing.assert_allclose(im2.array, im1.array, rtol=1.e-14, atol=1.e-14)
166
-
167
- # Moving by an integer number of pixels should be very close to the same image
168
- # over a different slice of the array.
169
- - im3 = psf.draw(x, y, chipnum, center=(x+1, y+3))
170
- + im3 = psf.draw(x, y, chipnum, center=(x+1, y+3), apodize=None)
171
- assert im3.bounds == im1.bounds
172
- # (Remember -- numpy indexing is y,x!)
173
- # Also, the FFTs will be different in detail, so only match to 1.e-6.
174
- @@ -788,14 +788,14 @@ def test_draw():
175
- # Can center at other locations, and the hsm centroids should come out centered pretty
176
- # close to that location.
177
- # (Of course the array will be different here, so can't test that.)
178
- - im4 = psf.draw(x, y, chipnum, center=(x+1.3,y-0.8))
179
- + im4 = psf.draw(x, y, chipnum, center=(x+1.3,y-0.8), apodize=None)
180
- assert im4.bounds == im1.bounds
181
- hsm = im4.FindAdaptiveMom()
182
- np.testing.assert_allclose(hsm.moments_centroid.x, x+1.3, atol=0.01)
183
- np.testing.assert_allclose(hsm.moments_centroid.y, y-0.8, atol=0.01)
184
-
185
- # Also allowed is center=True to place in the center of the image.
186
- - im5 = psf.draw(x, y, chipnum, center=True)
187
- + im5 = psf.draw(x, y, chipnum, center=True, apodize=None)
188
- assert im5.bounds == im1.bounds
189
- assert im5.array.shape == (48,48)
190
- np.testing.assert_allclose(im5.bounds.true_center.x, x, atol=0.5)
191
- @@ -814,7 +814,7 @@ def test_draw():
192
- # then center=True works fine to draw in the center of that image.
193
- im6 = im5.copy()
194
- im6.setCenter(0,0)
195
- - psf.draw(x, y, chipnum, center=True, image=im6)
196
- + psf.draw(x, y, chipnum, center=True, image=im6, apodize=None)
197
- assert im6.bounds.center == galsim.PositionI(0,0)
198
- np.testing.assert_allclose(im6.array.sum(), 1., rtol=1.e-3)
199
- hsm = im6.FindAdaptiveMom()
200
- @@ -824,7 +824,7 @@ def test_draw():
201
- np.testing.assert_allclose(im6.array, im5.array, rtol=1.e-14, atol=1.e-14)
202
-
203
- # Check non-even stamp size. Also, not unit flux while we're at it.
204
- - im7 = psf.draw(x, y, chipnum, center=(x+1.3,y-0.8), stamp_size=43, flux=23.7)
205
- + im7 = psf.draw(x, y, chipnum, center=(x+1.3,y-0.8), stamp_size=43, flux=23.7, apodize=None)
206
- assert im7.array.shape == (43,43)
207
- np.testing.assert_allclose(im7.bounds.true_center.x, x, atol=0.5)
208
- np.testing.assert_allclose(im7.bounds.true_center.y, y, atol=0.5)
209
- @@ -836,7 +836,7 @@ def test_draw():
210
- # Can't do mixed even/odd shape with stamp_size, but it will respect a provided image.
211
- im8 = galsim.Image(43,44)
212
- im8.setCenter(x,y) # It will respect the given bounds, so put it near the right place.
213
- - psf.draw(x, y, chipnum, center=(x+1.3,y-0.8), image=im8, flux=23.7)
214
- + psf.draw(x, y, chipnum, center=(x+1.3,y-0.8), image=im8, flux=23.7, apodize=None)
215
- assert im8.array.shape == (44,43)
216
- np.testing.assert_allclose(im8.array.sum(), 23.7, rtol=1.e-3)
217
- hsm = im8.FindAdaptiveMom()
218
- @@ -845,7 +845,7 @@ def test_draw():
219
-
220
- # The offset parameter can add an additional to whatever center is used.
221
- # Here center=None, so this is equivalent to im4 above.
222
- - im9 = psf.draw(x, y, chipnum, offset=(1.3,-0.8))
223
- + im9 = psf.draw(x, y, chipnum, offset=(1.3,-0.8), apodize=None)
224
- assert im9.bounds == im1.bounds
225
- hsm = im9.FindAdaptiveMom()
226
- np.testing.assert_allclose(im9.array, im4.array, rtol=1.e-14, atol=1.e-14)
227
- @@ -854,7 +854,7 @@ def test_draw():
228
- # use for this, but it's allowed. (The above with default center is used in unit
229
- # tests a number of times, so that version at least is useful if only for us.
230
- # I'm hard pressed to imaging end users wanting to specify things this way though.)
231
- - im10 = psf.draw(x, y, chipnum, center=(x+0.8, y-0.3), offset=(0.5,-0.5))
232
- + im10 = psf.draw(x, y, chipnum, center=(x+0.8, y-0.3), offset=(0.5,-0.5), apodize=None)
233
- assert im10.bounds == im1.bounds
234
- np.testing.assert_allclose(im10.array, im4.array, rtol=1.e-14, atol=1.e-14)
235
-
236
- diff --git a/tests/test_wcs.py b/tests/test_wcs.py
237
- index 2a373671..eaed388e 100644
238
- --- a/tests/test_wcs.py
239
- +++ b/tests/test_wcs.py
240
- @@ -366,7 +366,7 @@ def test_single():
241
-
242
- # This is the more user-friendly way to do this.
243
- # Equivalent to ~machine precision.
244
- - im = psf.draw(x, y, chipnum=chipnum)
245
- + im = psf.draw(x, y, chipnum=chipnum, apodize=None)
246
- print('im = ',im)
247
- print('star im = ',star.data.image)
248
- print('max diff = ',np.max(np.abs(im.array - star.data.image.array)))
249
- @@ -612,7 +612,7 @@ def test_olddes():
250
- print('area at 0,0 = ',psf.wcs[0].pixelArea(galsim.PositionD(0,0)),' = %f**2'%(
251
- psf.wcs[0].pixelArea(galsim.PositionD(0,0))**0.5))
252
- assert np.isclose(psf.wcs[0].pixelArea(galsim.PositionD(0,0)), 0.2628**2, rtol=1.e-3)
253
- - image = psf.draw(x=103.3, y=592.0, logger=logger)
254
- + image = psf.draw(x=103.3, y=592.0, logger=logger, apodize=None)
255
- print('image shape = ',image.array.shape)
256
- print('image near center = ',image.array[23:26,23:26])
257
- print('image sum = ',image.array.sum())
258
- @@ -628,7 +628,7 @@ def test_olddes():
259
-
260
- # Also check that it is picklable.
261
- psf2 = copy.deepcopy(psf)
262
- - image2 = psf2.draw(x=103.3, y=592.0)
263
- + image2 = psf2.draw(x=103.3, y=592.0, apodize=None)
264
- np.testing.assert_equal(image2.array, image.array)
265
-
266
- @timer
267
- @@ -668,7 +668,7 @@ def test_newdes():
268
- print('area at 0,0 = ',psf.wcs[0].pixelArea(galsim.PositionD(0,0)),' = %f**2'%(
269
- psf.wcs[0].pixelArea(galsim.PositionD(0,0))**0.5))
270
- assert np.isclose(psf.wcs[0].pixelArea(galsim.PositionD(0,0)), 0.263021**2, rtol=1.e-3)
271
- - image = psf.draw(x=103.3, y=592.0, logger=logger)
272
- + image = psf.draw(x=103.3, y=592.0, logger=logger, apodize=None)
273
- print('image shape = ',image.array.shape)
274
- print('image near center = ',image.array[23:26,23:26])
275
- print('image sum = ',image.array.sum())
276
- @@ -684,7 +684,7 @@ def test_newdes():
277
-
278
- # Also check that it is picklable.
279
- psf2 = copy.deepcopy(psf)
280
- - image2 = psf2.draw(x=103.3, y=592.0)
281
- + image2 = psf2.draw(x=103.3, y=592.0, apodize=None)
282
- np.testing.assert_equal(image2.array, image.array)
283
-
284
- @timer
82
+ diff --git a/tests/conftest.py b/tests/conftest.py
83
+ new file mode 100644
84
+ index 00000000..079d67ae
85
+ --- /dev/null
86
+ +++ b/tests/conftest.py
87
+ @@ -0,0 +1,4 @@
88
+ + # turn off apodization
89
+ + import piff.pixelgrid
90
+ +
91
+ + piff.pixelgrid.APODIZE_PARAMS = None
0 commit comments