roman-snpit-snappl 0.3.0__tar.gz → 0.5.0__tar.gz
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.
Potentially problematic release.
This version of roman-snpit-snappl might be problematic. Click here for more details.
- {roman_snpit_snappl-0.3.0/roman_snpit_snappl.egg-info → roman_snpit_snappl-0.5.0}/PKG-INFO +1 -1
- roman_snpit_snappl-0.5.0/changes/18.feature.rst +1 -0
- roman_snpit_snappl-0.5.0/changes/20.bugfix.rst +1 -0
- roman_snpit_snappl-0.5.0/changes/23.snappl.rst +1 -0
- roman_snpit_snappl-0.5.0/changes/26.feature.rst +3 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0/roman_snpit_snappl.egg-info}/PKG-INFO +1 -1
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/roman_snpit_snappl.egg-info/SOURCES.txt +4 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/snappl/_version.py +2 -2
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/snappl/image.py +51 -1
- roman_snpit_snappl-0.5.0/snappl/psf.py +438 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/snappl/wcs.py +3 -2
- roman_snpit_snappl-0.3.0/snappl/psf.py +0 -207
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/.cruft.json +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/.github/CODEOWNERS +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/.github/ISSUE_TEMPLATE/ISSUE_TEMPLATE.md +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/.github/ISSUE_TEMPLATE/PR_TEMPLATE.md +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/.github/dependabot.yml +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/.github/labeler.yml +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/.github/workflows/changelog.yml +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/.github/workflows/run_labeler.yml +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/.github/workflows/run_snappl_tests.yml +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/.github/workflows/sphinx-deploy.yml +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/.github/workflows/sub_package_update.yml +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/.gitignore +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/.pre-commit-config.yaml +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/CHANGES.rst +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/CITATION.cff +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/CODE_OF_CONDUCT.md +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/CONTRIBUTING.md +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/LICENSE +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/MANIFEST.in +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/README.rst +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/changes/.gitkeep +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/changes/10.snappl.rst +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/changes/13.bugfix.rst +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/changes/14.snappl.rst +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/changes/15.feature.rst +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/changes/16.feature.rst +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/changes/3.snappl.rst +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/changes/5.snappl.rst +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/changes/8.snappl.rst +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/changes/9.snappl.rst +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/codespell-ignore.txt +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/docs/Makefile +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/docs/_static/logo_black_filled.png +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/docs/changes.rst +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/docs/conf.py +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/docs/index.rst +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/docs/installation.rst +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/docs/make.bat +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/docs/usage.rst +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/licenses/.DS_Store +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/licenses/LICENSE.rst +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/licenses/README.rst +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/licenses/TEMPLATE_LICENSE.rst +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/pyproject.toml +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/roman_snpit_snappl.egg-info/dependency_links.txt +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/roman_snpit_snappl.egg-info/not-zip-safe +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/roman_snpit_snappl.egg-info/requires.txt +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/roman_snpit_snappl.egg-info/top_level.txt +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/setup.cfg +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/setup.py +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/snappl/__init__.py +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/snappl/_dev/__init__.py +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/snappl/_dev/scm_version.py +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/snappl/data/README.rst +0 -0
- {roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/tox.ini +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Add a few properties (sca, zeropoint, etc.) to Image ; add ou2024PSF.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Properly support creation of PSFs at non-integral pixel positions in OversampledImagePSF
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
AstropyWCS now automatically determines appropriate frame.
|
{roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/roman_snpit_snappl.egg-info/SOURCES.txt
RENAMED
|
@@ -29,6 +29,10 @@ changes/13.bugfix.rst
|
|
|
29
29
|
changes/14.snappl.rst
|
|
30
30
|
changes/15.feature.rst
|
|
31
31
|
changes/16.feature.rst
|
|
32
|
+
changes/18.feature.rst
|
|
33
|
+
changes/20.bugfix.rst
|
|
34
|
+
changes/23.snappl.rst
|
|
35
|
+
changes/26.feature.rst
|
|
32
36
|
changes/3.snappl.rst
|
|
33
37
|
changes/5.snappl.rst
|
|
34
38
|
changes/8.snappl.rst
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import types
|
|
2
|
+
import pathlib
|
|
2
3
|
|
|
3
4
|
import numpy as np
|
|
4
5
|
from astropy.io import fits
|
|
5
6
|
from astropy.nddata.utils import Cutout2D
|
|
6
7
|
# from astropy.coordinates import SkyCoord
|
|
7
8
|
|
|
9
|
+
import galsim.roman
|
|
10
|
+
|
|
8
11
|
from snpit_utils.logger import SNLogger
|
|
9
12
|
from snappl.wcs import AstropyWCS, GalsimWCS
|
|
10
13
|
|
|
@@ -63,11 +66,12 @@ class Image:
|
|
|
63
66
|
|
|
64
67
|
"""
|
|
65
68
|
self.inputs = types.SimpleNamespace()
|
|
66
|
-
self.inputs.path = path
|
|
69
|
+
self.inputs.path = pathlib.Path( path )
|
|
67
70
|
self.inputs.exposure = exposure
|
|
68
71
|
self.inputs.sca = sca
|
|
69
72
|
self._wcs = None # a BaseWCS object (in wcs.py)
|
|
70
73
|
self._is_cutout = False
|
|
74
|
+
self._zeropoint = None
|
|
71
75
|
|
|
72
76
|
@property
|
|
73
77
|
def data( self ):
|
|
@@ -101,6 +105,18 @@ class Image:
|
|
|
101
105
|
"""Tuple: (ny, nx) pixel size of image."""
|
|
102
106
|
raise NotImplementedError( f"{self.__class__.__name__} needs to implement image_shape" )
|
|
103
107
|
|
|
108
|
+
@property
|
|
109
|
+
def sca( self ):
|
|
110
|
+
return self.inputs.sca
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def path( self ):
|
|
114
|
+
return self.inputs.path
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def name( self ):
|
|
118
|
+
return self.inputs.path.name
|
|
119
|
+
|
|
104
120
|
@property
|
|
105
121
|
def sky_level( self ):
|
|
106
122
|
"""Estimate of the sky level in ADU."""
|
|
@@ -116,6 +132,24 @@ class Image:
|
|
|
116
132
|
"""Band (str)"""
|
|
117
133
|
raise NotImplementedError( f"{self.__class__.__name__} needs to implement band" )
|
|
118
134
|
|
|
135
|
+
@property
|
|
136
|
+
def zeropoint( self ):
|
|
137
|
+
"""Image zeropoint for AB magnitudes.
|
|
138
|
+
|
|
139
|
+
The zeropoint zp is defined so that an object with total counts
|
|
140
|
+
c has magnitude m:
|
|
141
|
+
|
|
142
|
+
m = -2.5 * log(10) + zp
|
|
143
|
+
|
|
144
|
+
"""
|
|
145
|
+
if self._zeropoint is None:
|
|
146
|
+
self._get_zeropoint()
|
|
147
|
+
return self._zeropoint
|
|
148
|
+
|
|
149
|
+
@zeropoint.setter
|
|
150
|
+
def zeropoint( self, val ):
|
|
151
|
+
self._zeropoint = val
|
|
152
|
+
|
|
119
153
|
@property
|
|
120
154
|
def mjd( self ):
|
|
121
155
|
"""MJD of the start of the image (defined how? TAI?)"""
|
|
@@ -192,7 +226,12 @@ class Image:
|
|
|
192
226
|
"""
|
|
193
227
|
raise NotImplementedError( f"{self.__class__.__name__} needs to implement get_wcs" )
|
|
194
228
|
|
|
229
|
+
def _get_zeropoint( self ):
|
|
230
|
+
"""Set self._zeropoint; see "zeropoint" property above."""
|
|
231
|
+
raise NotImplementedError( f"{self.__class__.__name__} needs to implement _get_zeropoint" )
|
|
232
|
+
|
|
195
233
|
def get_cutout(self, ra, dec, size):
|
|
234
|
+
|
|
196
235
|
"""Make a cutout of the image at the given RA and DEC.
|
|
197
236
|
|
|
198
237
|
Returns
|
|
@@ -504,3 +543,14 @@ class OpenUniverse2024FITSImage( FITSImage ):
|
|
|
504
543
|
"""The band the image is taken in (str)."""
|
|
505
544
|
header = self._get_header()
|
|
506
545
|
return header['FILTER'].strip()
|
|
546
|
+
|
|
547
|
+
@property
|
|
548
|
+
def mjd(self):
|
|
549
|
+
"""The mjd of the image."""
|
|
550
|
+
header = self._get_header()
|
|
551
|
+
return float( header['MJD-OBS'] )
|
|
552
|
+
|
|
553
|
+
@property
|
|
554
|
+
def _get_zeropoint( self ):
|
|
555
|
+
header = self._get_header()
|
|
556
|
+
return galsim.roman.getBandpasses()[self.band].zeropoint + header['ZPTMAG']
|
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
# IMPORTS Standard
|
|
2
|
+
import base64
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pathlib
|
|
5
|
+
import yaml
|
|
6
|
+
|
|
7
|
+
# IMPORTS Astro
|
|
8
|
+
import galsim
|
|
9
|
+
|
|
10
|
+
# IMPORTS Internal
|
|
11
|
+
from roman_imsim.utils import roman_utils
|
|
12
|
+
from snpit_utils.config import Config
|
|
13
|
+
from snpit_utils.logger import SNLogger
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PSF:
|
|
17
|
+
# Thought required: how to deal with oversampling.
|
|
18
|
+
|
|
19
|
+
def __init__( self, *args, **kwargs ):
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
# This is here for backwards compatibility
|
|
23
|
+
@property
|
|
24
|
+
def clip_size( self ):
|
|
25
|
+
return self.stamp_size
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def stamp_size( self ):
|
|
29
|
+
"""The size of the one side of a PSF image stamp at image resolution. Is always odd."""
|
|
30
|
+
raise NotImplementedError( f"{self.__class__.__name__} needs to implement stamp_size." )
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_stamp( self, x, y, flux=1. ):
|
|
34
|
+
"""Return a 2d numpy image of the PSF at the image resolution.
|
|
35
|
+
|
|
36
|
+
The PSF will be centered as best possible on the stamp*. So, if
|
|
37
|
+
x ends in 0.8, it will be left of center, and if x ends in 0.2,
|
|
38
|
+
it will be right of center. If the fractional part of x or y is
|
|
39
|
+
exactly 0.5, there's an ambituity as to where on the image you
|
|
40
|
+
should place the stamp of the PSF. The position of the PSF on
|
|
41
|
+
the returned stamp will always round *down* in this case. (The
|
|
42
|
+
pixel on the image that corresponds to the center pixel on the
|
|
43
|
+
clip is at floor(x+0.5),floor(y+0.5), *not*
|
|
44
|
+
round(x+0.5),round(y+0.5). Those two things are different, and
|
|
45
|
+
round is not consistent; see the comment in
|
|
46
|
+
OversampledImagePSF.get_stamp for more if you care.)
|
|
47
|
+
|
|
48
|
+
So, for example, assuming the PSF is intrinsically centered*,
|
|
49
|
+
if the stamp size is 5×5 and you ask for the PSF at x=1023,
|
|
50
|
+
y=1023, then you're going to want to put the stamp on to the
|
|
51
|
+
image at image[1021:1026,1021:1026]. However, if you ask for
|
|
52
|
+
the PSF at x=1023.5,y=1023., you'll want to put the stamp on the
|
|
53
|
+
image at image[1021:1026,1022:1027]. (Remember that default
|
|
54
|
+
numpy arrays of astronomy images are indexed [y,x].)
|
|
55
|
+
|
|
56
|
+
* "The PSF will be centered as best possible on the stamp": this
|
|
57
|
+
is only true if the PSF itself is intrinsically centered. See
|
|
58
|
+
OversampledImagePSF.create for a discussion of
|
|
59
|
+
non-intrinsically-centered PSFs.
|
|
60
|
+
|
|
61
|
+
Parameters
|
|
62
|
+
----------
|
|
63
|
+
x: float
|
|
64
|
+
Position on the image of the center of the psf. If not
|
|
65
|
+
given, defaults to something sensible that was defined when
|
|
66
|
+
the object was constructed. If you want to do sub-pixel
|
|
67
|
+
shifts, then the fractional part of x will (usually) not be
|
|
68
|
+
0.
|
|
69
|
+
|
|
70
|
+
y: float
|
|
71
|
+
Position on the image of the center of the psf. Same kind
|
|
72
|
+
of default as x.
|
|
73
|
+
|
|
74
|
+
flux: float, default 1.
|
|
75
|
+
Make the sum of the clip this. If None, just let the clip
|
|
76
|
+
be scaled however it's naturally scaled. For some
|
|
77
|
+
subclasses, that may be what you actually want.
|
|
78
|
+
|
|
79
|
+
Returns
|
|
80
|
+
-------
|
|
81
|
+
2d numpy array
|
|
82
|
+
|
|
83
|
+
"""
|
|
84
|
+
raise NotImplementedError( f"{self.__class__.__name__} needs to implement get_stamp" )
|
|
85
|
+
|
|
86
|
+
@classmethod
|
|
87
|
+
def get_psf_object( cls, psfclass, **kwargs ):
|
|
88
|
+
"""Return a PSF object whose type is specified by psfclass.
|
|
89
|
+
|
|
90
|
+
Parameters
|
|
91
|
+
----------
|
|
92
|
+
psfclass : str
|
|
93
|
+
The name of the class of the PSF to instantiate.
|
|
94
|
+
|
|
95
|
+
**kwargs : further keyword arguments
|
|
96
|
+
TODO : we need to standardize on these so that things can
|
|
97
|
+
just call PSF.get_psf_object() without having to have their
|
|
98
|
+
own if statements on the type to figure out what kwargs to
|
|
99
|
+
pass!
|
|
100
|
+
|
|
101
|
+
"""
|
|
102
|
+
if psfclass == "OversampledImagePSF":
|
|
103
|
+
return OversampledImagePSF.create( **kwargs )
|
|
104
|
+
|
|
105
|
+
if psfclass == "YamlSerialized_OversampledImagePSF":
|
|
106
|
+
return YamlSerialized_OversampledImagePSF( **kwargs )
|
|
107
|
+
|
|
108
|
+
if psfclass == "A25ePSF":
|
|
109
|
+
return A25ePSF( **kwargs )
|
|
110
|
+
|
|
111
|
+
if psfclass == "ou24PSF":
|
|
112
|
+
return ou24PSF( **kwargs )
|
|
113
|
+
|
|
114
|
+
raise ValueError( f"Unknown PSF class {psfclass}" )
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class OversampledImagePSF( PSF ):
|
|
118
|
+
@classmethod
|
|
119
|
+
def create( cls, data=None, x=None, y=None, oversample_factor=1., enforce_odd=True, normalize=True, **kwargs ):
|
|
120
|
+
|
|
121
|
+
"""Parameters
|
|
122
|
+
----------
|
|
123
|
+
data: 2d numpy array
|
|
124
|
+
The image data of the oversampled PSF. Required.
|
|
125
|
+
|
|
126
|
+
x, y: float
|
|
127
|
+
Position on the source image where this PSF is evaluated.
|
|
128
|
+
Required. Most of the time, but not always, you probably
|
|
129
|
+
want x and y to be integer values. (As in, not integer
|
|
130
|
+
typed, but floats that satisfy x-floor(x)=0.) These are
|
|
131
|
+
also the defaults that get_stamp will use if x and y are not
|
|
132
|
+
passed to get_stamp.
|
|
133
|
+
|
|
134
|
+
If x and/or y have nonzero fractional parts, then the data
|
|
135
|
+
array must be consistent. First consider non-oversampled
|
|
136
|
+
data. Suppose you pass a 11×11 array with x=1022.5 and
|
|
137
|
+
y=1023.25. In this case, the peak of a perfectly symmetric
|
|
138
|
+
PSF image on data would be at (4.5, 5.25). (Not (5.5,
|
|
139
|
+
5.25)! If something's at *exactly* .5, always round down
|
|
140
|
+
here regardless of wheter the integer part is even or odd.)
|
|
141
|
+
The center pixel and the one to the right of it should have
|
|
142
|
+
the same brightness, and the pixel just below center should
|
|
143
|
+
be dimmer than the pixel just above center.
|
|
144
|
+
|
|
145
|
+
For oversampled psfs, the data array must be properly
|
|
146
|
+
shifted to account for non-integral x and y. The shift will
|
|
147
|
+
be as in non-oversampled data, only multiplied by the
|
|
148
|
+
oversampling factor. So, in the same example, if you
|
|
149
|
+
specify a peak of (4.5, 5.25), and you have an oversampling
|
|
150
|
+
factor of 3, you should pass a 33×33 array with the peak of
|
|
151
|
+
the PSF (assuming a symmetric PSF) at (14.5, 16.75).
|
|
152
|
+
|
|
153
|
+
oversample_factor: float, default 1.
|
|
154
|
+
There are this many pixels along one axis in data for one pixel in the original image.
|
|
155
|
+
|
|
156
|
+
enforce_odd: bool, default True
|
|
157
|
+
Enforce x_edges and y_edges having an odd width.
|
|
158
|
+
|
|
159
|
+
normalize: bool, default True
|
|
160
|
+
Make sure internally stored PSF sums to 1 ; you usually don't want to change this.
|
|
161
|
+
|
|
162
|
+
Returns
|
|
163
|
+
-------
|
|
164
|
+
object of type cls
|
|
165
|
+
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
if len(kwargs) > 0:
|
|
169
|
+
SNLogger.warning( f"Unused arguments to OversampledImagePSF.create: {[k for k in kwargs]}" )
|
|
170
|
+
|
|
171
|
+
# TODO : implement enforce_odd
|
|
172
|
+
# TODO : enforce square
|
|
173
|
+
|
|
174
|
+
if not isinstance( data, np.ndarray ) or ( len(data.shape) != 2 ):
|
|
175
|
+
raise TypeError( "data must be a 2d numpy array" )
|
|
176
|
+
|
|
177
|
+
x = float( x )
|
|
178
|
+
y = float( y )
|
|
179
|
+
|
|
180
|
+
psf = cls()
|
|
181
|
+
psf._data = data
|
|
182
|
+
if normalize:
|
|
183
|
+
psf._data /= psf._data.sum()
|
|
184
|
+
psf._x = x
|
|
185
|
+
psf._y = y
|
|
186
|
+
psf._oversamp = oversample_factor
|
|
187
|
+
return psf
|
|
188
|
+
|
|
189
|
+
@property
|
|
190
|
+
def x( self ):
|
|
191
|
+
return self._x
|
|
192
|
+
|
|
193
|
+
@property
|
|
194
|
+
def y( self ):
|
|
195
|
+
return self._y
|
|
196
|
+
|
|
197
|
+
@property
|
|
198
|
+
def x0( self ):
|
|
199
|
+
return self._x0
|
|
200
|
+
|
|
201
|
+
@property
|
|
202
|
+
def y0( self ):
|
|
203
|
+
return self._x0
|
|
204
|
+
|
|
205
|
+
@property
|
|
206
|
+
def oversample_factor( self ):
|
|
207
|
+
return self._oversamp
|
|
208
|
+
|
|
209
|
+
@property
|
|
210
|
+
def oversampled_data( self ):
|
|
211
|
+
return self._data
|
|
212
|
+
|
|
213
|
+
@property
|
|
214
|
+
def stamp_size( self ):
|
|
215
|
+
"""The size of the PSF image clip at image resolution. Is always odd."""
|
|
216
|
+
sz = int( np.floor( self._data.shape[0] / self._oversamp ) )
|
|
217
|
+
sz += 1 if sz % 2 == 0 else 0
|
|
218
|
+
return sz
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def __init__( self, *args, **kwargs ):
|
|
222
|
+
super().__init__( *args, **kwargs )
|
|
223
|
+
self._data = None
|
|
224
|
+
self._x = None
|
|
225
|
+
self._y = None
|
|
226
|
+
self._x0 = None
|
|
227
|
+
self._y0 = None
|
|
228
|
+
self._oversamp = None
|
|
229
|
+
|
|
230
|
+
def get_stamp( self, x=None, y=None, flux=1. ):
|
|
231
|
+
# (x, y) is the position on the image for which we want to render the PSF.
|
|
232
|
+
x = float(x) if x is not None else self._x
|
|
233
|
+
y = float(y) if y is not None else self._y
|
|
234
|
+
|
|
235
|
+
# (xc, yc) is the closest pixel center to (x, y) on the image--
|
|
236
|
+
#
|
|
237
|
+
# round() isn't the right thing to use here, because it will
|
|
238
|
+
# behave differently when x - round(x) = 0.5 based on whether
|
|
239
|
+
# floor(x) is even or odd. What we *want* is for the psf to
|
|
240
|
+
# be as close to the center of the clip as possible. In the
|
|
241
|
+
# case where the fractional part of x is exactly 0.5, it's
|
|
242
|
+
# ambiguous what that means-- there are four places you could
|
|
243
|
+
# stick the PSF to statisfy that criterion. By using
|
|
244
|
+
# floor(x+0.5), we will consistently have the psf leaning down
|
|
245
|
+
# and to the left when the fractional part of x (and y) is
|
|
246
|
+
# exactly 0.5, whereas using round would give different
|
|
247
|
+
# results based on the integer part of x (and y).
|
|
248
|
+
xc = int( np.floor( x + 0.5 ) )
|
|
249
|
+
yc = int( np.floor( y + 0.5 ) )
|
|
250
|
+
|
|
251
|
+
# (natx, naty) is the "natural position" on the image for the
|
|
252
|
+
# psf. This is simply (int(x), int(y)) if the fractional part
|
|
253
|
+
# of x and y are zero. Otherwise, it rounds to the closest
|
|
254
|
+
# pixel... unless the fractional part is exactly 0.5, in which
|
|
255
|
+
# case we do floor(x+0.5) instead of round(x) as described above.
|
|
256
|
+
natx = int( np.floor( self._x + 0.5 ) )
|
|
257
|
+
naty = int( np.floor( self._y + 0.5 ) )
|
|
258
|
+
# natxfrac and natyfrac kinda the negative of the fractional
|
|
259
|
+
# part of natx and naty. They will be in the range (-0.5,
|
|
260
|
+
# 0.5]
|
|
261
|
+
natxfrac = natx - self._x
|
|
262
|
+
natyfrac = naty - self._y
|
|
263
|
+
|
|
264
|
+
# See Chapter 5, "How PSFEx Works", of the PSFEx manual
|
|
265
|
+
# https://psfex.readthedocs.io/en/latest/Working.html
|
|
266
|
+
# We're using this method for both image and psfex PSFs,
|
|
267
|
+
# as the interpolation is more general than PSFEx:
|
|
268
|
+
# https://en.wikipedia.org/wiki/Lanczos_resampling
|
|
269
|
+
# ...though of course, the choice of a=4 comes from PSFEx.
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
psfwid = self._data.shape[0]
|
|
273
|
+
stampwid = self.clip_size
|
|
274
|
+
|
|
275
|
+
psfdex1d = np.arange( -( psfwid//2), psfwid//2+1, dtype=int )
|
|
276
|
+
|
|
277
|
+
# If the returned clip is to be added to the image, it should
|
|
278
|
+
# be added to image[ymin:ymax, xmin:xmax].
|
|
279
|
+
xmin = xc - stampwid // 2
|
|
280
|
+
xmax = xc + stampwid // 2 + 1
|
|
281
|
+
ymin = yc - stampwid // 2
|
|
282
|
+
ymax = yc + stampwid // 2 + 1
|
|
283
|
+
|
|
284
|
+
psfsamp = 1. / self._oversamp
|
|
285
|
+
xs = np.arange( xmin, xmax )
|
|
286
|
+
ys = np.arange( ymin, ymax )
|
|
287
|
+
xsincarg = psfdex1d[:, np.newaxis] - ( xs - natxfrac - x ) / psfsamp
|
|
288
|
+
xsincvals = np.sinc( xsincarg ) * np.sinc( xsincarg/4. )
|
|
289
|
+
xsincvals[ ( xsincarg > 4 ) | ( xsincarg < -4 ) ] = 0.
|
|
290
|
+
ysincarg = psfdex1d[:, np.newaxis] - ( ys - natyfrac - y ) / psfsamp
|
|
291
|
+
ysincvals = np.sinc( ysincarg ) * np.sinc( ysincarg/4. )
|
|
292
|
+
ysincvals[ ( ysincarg > 4 ) | ( ysincarg < -4 ) ] = 0.
|
|
293
|
+
tenpro = np.tensordot( ysincvals[:, :, np.newaxis], xsincvals[:, :, np.newaxis], axes=0 )[ :, :, 0, :, :, 0 ]
|
|
294
|
+
clip = ( self._data[:, np.newaxis, :, np.newaxis ] * tenpro ).sum( axis=0 ).sum( axis=1 )
|
|
295
|
+
|
|
296
|
+
# Keeping the code below, because the code above is inpenetrable, and it's trying to
|
|
297
|
+
# do the same thing as the code below.
|
|
298
|
+
# (I did emprically test it using the PSFs from the test_psf.py::test_psfex_rendering,
|
|
299
|
+
# and it worked. In particular, there is not a transposition error in the "tenpro=" line;
|
|
300
|
+
# if you swap the order of yxincvals and xsincvals in the test, then the values of clip
|
|
301
|
+
# do not match the code below very well. As is, they match to within a few times 1e-17,
|
|
302
|
+
# which is good enough as the minimum non-zero value in either one is of order 1e-12.)
|
|
303
|
+
# clip = np.empty( ( stampwid, stampwid ), dtype=dtype )
|
|
304
|
+
# for xi in range( xmin, xmax ):
|
|
305
|
+
# for yi in range( ymin, ymax ):
|
|
306
|
+
# xsincarg = psfdex1d - (xi-x) / psfsamp
|
|
307
|
+
# xsincvals = np.sinc( xsincarg ) * np.sinc( xsincarg/4. )
|
|
308
|
+
# xsincvals[ ( xsincarg > 4 ) | ( xsincarg < -4 ) ] = 0
|
|
309
|
+
# ysincarg = psfdex1d - (yi-y) / psfsamp
|
|
310
|
+
# ysincvals = np.sinc( ysincarg ) * np.sinc( ysincarg/4. )
|
|
311
|
+
# ysincvals[ ( ysincarg > 4 ) | ( ysincarg < -4 ) ] = 0
|
|
312
|
+
# clip[ yi-ymin, xi-xmin ] = ( xsincvals[np.newaxis, :]
|
|
313
|
+
# * ysincvals[:, np.newaxis]
|
|
314
|
+
# * psfbase ).sum()
|
|
315
|
+
|
|
316
|
+
clip *= flux / clip.sum()
|
|
317
|
+
|
|
318
|
+
return clip
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
class YamlSerialized_OversampledImagePSF( OversampledImagePSF ):
|
|
322
|
+
|
|
323
|
+
def __init__( self, *args, **kwargs ):
|
|
324
|
+
super().__init__( *args, **kwargs )
|
|
325
|
+
|
|
326
|
+
def read( self, filepath ):
|
|
327
|
+
y = yaml.safe_load( open( filepath ) )
|
|
328
|
+
self._x = y['x0']
|
|
329
|
+
self._y = y['y0']
|
|
330
|
+
self._oversamp = y['oversamp']
|
|
331
|
+
self._data = np.frombuffer( base64.b64decode( y['data'] ), dtype=y['dtype'] )
|
|
332
|
+
self._data = self._data.reshape( ( y['shape0'], y['shape1'] ) )
|
|
333
|
+
|
|
334
|
+
def write( self, filepath ):
|
|
335
|
+
out = { 'x0': float( self._x ),
|
|
336
|
+
'y0': float( self._y ),
|
|
337
|
+
'oversamp': self._oversamp,
|
|
338
|
+
'shape0': self._data.shape[0],
|
|
339
|
+
'shape1': self._data.shape[1],
|
|
340
|
+
'dtype': str( self._data.dtype ),
|
|
341
|
+
# TODO : make this right, think about endian-ness, etc.
|
|
342
|
+
'data': base64.b64encode( self._data.tobytes() ).decode( 'utf-8' ) }
|
|
343
|
+
# TODO : check overwriting etc.
|
|
344
|
+
yaml.dump( out, open( filepath, 'w' ) )
|
|
345
|
+
|
|
346
|
+
class A25ePSF( YamlSerialized_OversampledImagePSF ):
|
|
347
|
+
|
|
348
|
+
def __init__( self, band, sca, x, y, *args, **kwargs ):
|
|
349
|
+
|
|
350
|
+
super().__init__( *args, **kwargs )
|
|
351
|
+
|
|
352
|
+
cfg = Config.get()
|
|
353
|
+
basepath = pathlib.Path( cfg.value( 'photometry.snappl.A25ePSF_path' ) )
|
|
354
|
+
|
|
355
|
+
"""
|
|
356
|
+
The array size is the size of one image (nx, ny).
|
|
357
|
+
The grid size is the number of times we divide that image
|
|
358
|
+
into smaller parts for the purposes of assigning the
|
|
359
|
+
correct ePSF (8 x 8 = 64 ePSFs).
|
|
360
|
+
|
|
361
|
+
4088 px/8 = 511 px. So, int(arr_size/gridsize) is just a type
|
|
362
|
+
conversion. In the future, we may have a class where these things
|
|
363
|
+
are variable, but for now, we are using only the 8 x 8 grid of
|
|
364
|
+
ePSFs from Aldoroty et al. 2025a. So, it's hardcoded.
|
|
365
|
+
|
|
366
|
+
"""
|
|
367
|
+
arr_size = 4088
|
|
368
|
+
gridsize = 8
|
|
369
|
+
cutoutsize = int(arr_size/gridsize)
|
|
370
|
+
grid_centers = np.linspace(0.5 * cutoutsize, arr_size - 0.5 * cutoutsize, gridsize)
|
|
371
|
+
|
|
372
|
+
dist_x = np.abs(grid_centers - x)
|
|
373
|
+
dist_y = np.abs(grid_centers - y)
|
|
374
|
+
|
|
375
|
+
x_idx = np.argmin(dist_x)
|
|
376
|
+
y_idx = np.argmin(dist_y)
|
|
377
|
+
|
|
378
|
+
x_cen = grid_centers[x_idx]
|
|
379
|
+
y_cen = grid_centers[y_idx]
|
|
380
|
+
|
|
381
|
+
min_mag = 19.0
|
|
382
|
+
max_mag = 21.5
|
|
383
|
+
psfpath = basepath / band / str(sca) / f'{cutoutsize}_{x_cen:.1f}_{y_cen:.1f}_-_{min_mag}_{max_mag}_-_{band}_{sca}.psf'
|
|
384
|
+
|
|
385
|
+
self.read(psfpath)
|
|
386
|
+
|
|
387
|
+
class ou24PSF( PSF ):
|
|
388
|
+
# Currently, does not support any oversampling, because SFFT doesn't
|
|
389
|
+
# TODO: support oversampling!
|
|
390
|
+
|
|
391
|
+
def __init__( self, pointing=None, sca=None, config_file=None, size=201, include_photonOps=True, **kwargs ):
|
|
392
|
+
if len(kwargs) > 0:
|
|
393
|
+
SNLogger.warning( f"Unused arguments to ou24PSF.__init__: {[k for k in kwargs]}" )
|
|
394
|
+
|
|
395
|
+
if ( pointing is None ) or ( sca is None ):
|
|
396
|
+
raise ValueError( "Need a pointing and an sca to make an ou24PSF" )
|
|
397
|
+
if ( size % 2 == 0 ) or ( int(size) != size ):
|
|
398
|
+
raise ValueError( "Size must be an odd integer." )
|
|
399
|
+
size = int( size )
|
|
400
|
+
|
|
401
|
+
if config_file is None:
|
|
402
|
+
config_file = Config.get().value( 'ou24psf.config_file' )
|
|
403
|
+
self.config_file = config_file
|
|
404
|
+
self.pointing = pointing
|
|
405
|
+
self.sca = sca
|
|
406
|
+
self.size = size
|
|
407
|
+
self.include_photonOps = include_photonOps
|
|
408
|
+
self._stamps = {}
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
@property
|
|
412
|
+
def stamp_size( self ):
|
|
413
|
+
return self.size
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def get_stamp( self, x, y, flux=1., seed=None ):
|
|
417
|
+
"""Return a 2d numpy image of the PSF at the image resolution.
|
|
418
|
+
|
|
419
|
+
Parameters are as in PSF.get_stamp, plus:
|
|
420
|
+
|
|
421
|
+
Parameters
|
|
422
|
+
----------
|
|
423
|
+
seed : int
|
|
424
|
+
A random seed to pass to galsim.BaseDeviate for photonOps.
|
|
425
|
+
NOTE: this is not part of the base PSF interface (at least,
|
|
426
|
+
as of yet), so don't use it in production pipeline code.
|
|
427
|
+
However, it will be useful in tests for purposes of testing
|
|
428
|
+
reproducibility.
|
|
429
|
+
|
|
430
|
+
"""
|
|
431
|
+
if (x, y) not in self._stamps:
|
|
432
|
+
rmutils = roman_utils( self.config_file, self.pointing, self.sca )
|
|
433
|
+
if seed is not None:
|
|
434
|
+
rmutils.rng = galsim.BaseDeviate( seed )
|
|
435
|
+
self._stamps[(x, y)] = rmutils.getPSF_Image( self.size, x, y,
|
|
436
|
+
include_photonOps=self.include_photonOps ).array
|
|
437
|
+
self._stamps[(x, y)] *= flux / self._stamps[(x, y)].sum()
|
|
438
|
+
return self._stamps[(x, y)]
|
|
@@ -118,8 +118,9 @@ class AstropyWCS(BaseWCS):
|
|
|
118
118
|
dec = float( dec )
|
|
119
119
|
return ra, dec
|
|
120
120
|
|
|
121
|
-
def world_to_pixel( self, ra, dec
|
|
122
|
-
|
|
121
|
+
def world_to_pixel( self, ra, dec):
|
|
122
|
+
frame = self._wcs.wcs.radesys.lower() # Needs to be lowercase for SkyCoord
|
|
123
|
+
scs = SkyCoord( ra, dec, unit=(u.deg, u.deg), frame = frame)
|
|
123
124
|
x, y = self._wcs.world_to_pixel( scs )
|
|
124
125
|
if not ( isinstance( ra, collections.abc.Sequence )
|
|
125
126
|
or ( isinstance( ra, np.ndarray ) and y.size > 1 )
|
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
import yaml
|
|
2
|
-
import base64
|
|
3
|
-
|
|
4
|
-
import numpy as np
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class PSF:
|
|
8
|
-
def __init__( self, *args, **kwargs ):
|
|
9
|
-
# Will define a PSF with a nominal position
|
|
10
|
-
pass
|
|
11
|
-
|
|
12
|
-
def get_stamp( self, x, y, flux=1. ):
|
|
13
|
-
"""Return a 2d numpy image of the PSF at the image resolution.
|
|
14
|
-
|
|
15
|
-
Parameters
|
|
16
|
-
----------
|
|
17
|
-
x: float
|
|
18
|
-
Position on the image of the center of the psf
|
|
19
|
-
|
|
20
|
-
y: float
|
|
21
|
-
Position on the image of the center of the psf
|
|
22
|
-
|
|
23
|
-
x0: float or None
|
|
24
|
-
Image position of the center of the stamp; defaults to FIGURE THIS OUT
|
|
25
|
-
|
|
26
|
-
y0: float or None
|
|
27
|
-
|
|
28
|
-
flux: float, default 1.
|
|
29
|
-
Make the sum of the clip this
|
|
30
|
-
|
|
31
|
-
Returns
|
|
32
|
-
-------
|
|
33
|
-
2d numpy array
|
|
34
|
-
|
|
35
|
-
"""
|
|
36
|
-
raise NotImplementedError( f"{self.__class__.__name__} needs to implement get_stamp" )
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
class OversampledImagePSF( PSF ):
|
|
41
|
-
@classmethod
|
|
42
|
-
def create( cls, data, x0, y0, oversample_factor=1., enforce_odd=True, normalize=True ):
|
|
43
|
-
"""Parameters
|
|
44
|
-
----------
|
|
45
|
-
data: 2d numpy array
|
|
46
|
-
|
|
47
|
-
x0, y0: float
|
|
48
|
-
Position on the source image where this PSF is evaluated
|
|
49
|
-
|
|
50
|
-
oversample_factor: float, default 1.
|
|
51
|
-
There are this many pixels along one axis in data for one pixel in the original image
|
|
52
|
-
|
|
53
|
-
enforce_odd: bool, default True
|
|
54
|
-
Enforce x_edges and y_edges having an odd width.
|
|
55
|
-
|
|
56
|
-
normalize: bool, default True
|
|
57
|
-
Make sure internally stored PSF sums to 1 ; you usually don't want to change this.
|
|
58
|
-
|
|
59
|
-
Returns
|
|
60
|
-
-------
|
|
61
|
-
object of type cls
|
|
62
|
-
|
|
63
|
-
"""
|
|
64
|
-
# TODO : implement enforce_odd
|
|
65
|
-
# TODO : enforce square
|
|
66
|
-
|
|
67
|
-
psf = cls()
|
|
68
|
-
psf._data = data
|
|
69
|
-
if normalize:
|
|
70
|
-
psf._data /= psf._data.sum()
|
|
71
|
-
psf._x0 = x0
|
|
72
|
-
psf._y0 = y0
|
|
73
|
-
psf._oversamp = oversample_factor
|
|
74
|
-
return psf
|
|
75
|
-
|
|
76
|
-
@property
|
|
77
|
-
def x0( self ):
|
|
78
|
-
return self._x0
|
|
79
|
-
|
|
80
|
-
@property
|
|
81
|
-
def y0( self ):
|
|
82
|
-
return self._x0
|
|
83
|
-
|
|
84
|
-
@property
|
|
85
|
-
def oversample_factor( self ):
|
|
86
|
-
return self._oversamp
|
|
87
|
-
|
|
88
|
-
@property
|
|
89
|
-
def oversampled_data( self ):
|
|
90
|
-
return self._data
|
|
91
|
-
|
|
92
|
-
@property
|
|
93
|
-
def clip_size( self ):
|
|
94
|
-
"""The size of the PSF image clip at image resolution."""
|
|
95
|
-
return int( np.floor( self._data.shape[0] / self._oversamp ) )
|
|
96
|
-
|
|
97
|
-
def __init__( self, *args, **kwargs ):
|
|
98
|
-
super().__init__( *args, **kwargs )
|
|
99
|
-
self._data = None
|
|
100
|
-
self._x0 = None
|
|
101
|
-
self._y0 = None
|
|
102
|
-
self._oversamp = None
|
|
103
|
-
|
|
104
|
-
def get_stamp( self, x=None, y=None, normalize=True ):
|
|
105
|
-
x = float(x) if x is not None else self._x0
|
|
106
|
-
y = float(y) if y is not None else self._y0
|
|
107
|
-
|
|
108
|
-
# round() isn't the right thing to use here, because it will
|
|
109
|
-
# behave differently when x - round(x) = 0.5 based on whether
|
|
110
|
-
# floor(x) is even or odd. What we *want* is for the psf to
|
|
111
|
-
# be as close to the center of the clip as possible. In the
|
|
112
|
-
# case where the fractional part of x is exactly 0.5, it's
|
|
113
|
-
# ambiguous what that means-- there are four places you could
|
|
114
|
-
# stick the PSF to statisfy that criterion. By using
|
|
115
|
-
# floor(x+0.5), we will consistently have the psf leaning down
|
|
116
|
-
# and to the left when the fractional part of x (and y) is
|
|
117
|
-
# exactly 0.5, whereas using round would give different
|
|
118
|
-
# results based on the integer part of x (and y).
|
|
119
|
-
|
|
120
|
-
xc = int( np.floor( x + 0.5 ) )
|
|
121
|
-
yc = int( np.floor( y + 0.5 ) )
|
|
122
|
-
|
|
123
|
-
# See Chapter 5, "How PSFEx Works", of the PSFEx manual
|
|
124
|
-
# https://psfex.readthedocs.io/en/latest/Working.html
|
|
125
|
-
# We're using this method for both image and psfex PSFs,
|
|
126
|
-
# as the interpolation is more general than PSFEx:
|
|
127
|
-
# https://en.wikipedia.org/wiki/Lanczos_resampling
|
|
128
|
-
# ...though of course, the choice of a=4 comes from PSFEx.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
psfwid = self._data.shape[0]
|
|
132
|
-
stampwid = self.clip_size
|
|
133
|
-
stampwid += 1 if stampwid % 2 == 0 else 0
|
|
134
|
-
|
|
135
|
-
psfdex1d = np.arange( -( psfwid//2), psfwid//2+1, dtype=int )
|
|
136
|
-
|
|
137
|
-
xmin = xc - stampwid // 2
|
|
138
|
-
xmax = xc + stampwid // 2 + 1
|
|
139
|
-
ymin = yc - stampwid // 2
|
|
140
|
-
ymax = yc + stampwid // 2 + 1
|
|
141
|
-
|
|
142
|
-
psfsamp = 1. / self._oversamp
|
|
143
|
-
xs = np.array( range( xmin, xmax ) )
|
|
144
|
-
ys = np.array( range( ymin, ymax ) )
|
|
145
|
-
xsincarg = psfdex1d[:, np.newaxis] - ( xs - x ) / psfsamp
|
|
146
|
-
xsincvals = np.sinc( xsincarg ) * np.sinc( xsincarg/4. )
|
|
147
|
-
xsincvals[ ( xsincarg > 4 ) | ( xsincarg < -4 ) ] = 0.
|
|
148
|
-
ysincarg = psfdex1d[:, np.newaxis] - ( ys - y ) / psfsamp
|
|
149
|
-
ysincvals = np.sinc( ysincarg ) * np.sinc( ysincarg/4. )
|
|
150
|
-
ysincvals[ ( ysincarg > 4 ) | ( ysincarg < -4 ) ] = 0.
|
|
151
|
-
tenpro = np.tensordot( ysincvals[:, :, np.newaxis], xsincvals[:, :, np.newaxis], axes=0 )[ :, :, 0, :, :, 0 ]
|
|
152
|
-
clip = ( self._data[:, np.newaxis, :, np.newaxis ] * tenpro ).sum( axis=0 ).sum( axis=1 )
|
|
153
|
-
|
|
154
|
-
# Keeping the code below, because the code above is inpenetrable, and it's trying to
|
|
155
|
-
# do the same thing as the code below.
|
|
156
|
-
# (I did emprically test it using the PSFs from the test_psf.py::test_psfex_rendering,
|
|
157
|
-
# and it worked. In particular, there is not a transposition error in the "tenpro=" line;
|
|
158
|
-
# if you swap the order of yxincvals and xsincvals in the test, then the values of clip
|
|
159
|
-
# do not match the code below very well. As is, they match to within a few times 1e-17,
|
|
160
|
-
# which is good enough as the minimum non-zero value in either one is of order 1e-12.)
|
|
161
|
-
# clip = np.empty( ( stampwid, stampwid ), dtype=dtype )
|
|
162
|
-
# for xi in range( xmin, xmax ):
|
|
163
|
-
# for yi in range( ymin, ymax ):
|
|
164
|
-
# xsincarg = psfdex1d - (xi-x) / psfsamp
|
|
165
|
-
# xsincvals = np.sinc( xsincarg ) * np.sinc( xsincarg/4. )
|
|
166
|
-
# xsincvals[ ( xsincarg > 4 ) | ( xsincarg < -4 ) ] = 0
|
|
167
|
-
# ysincarg = psfdex1d - (yi-y) / psfsamp
|
|
168
|
-
# ysincvals = np.sinc( ysincarg ) * np.sinc( ysincarg/4. )
|
|
169
|
-
# ysincvals[ ( ysincarg > 4 ) | ( ysincarg < -4 ) ] = 0
|
|
170
|
-
# clip[ yi-ymin, xi-xmin ] = ( xsincvals[np.newaxis, :]
|
|
171
|
-
# * ysincvals[:, np.newaxis]
|
|
172
|
-
# * psfbase ).sum()
|
|
173
|
-
|
|
174
|
-
if normalize:
|
|
175
|
-
clip /= clip.sum()
|
|
176
|
-
|
|
177
|
-
return clip
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
class YamlSerialized_OversampledImagePSF( OversampledImagePSF ):
|
|
181
|
-
|
|
182
|
-
def __init__( self, *args, **kwargs ):
|
|
183
|
-
super().__init__( *args, **kwargs )
|
|
184
|
-
|
|
185
|
-
def read( self, filepath ):
|
|
186
|
-
y = yaml.safe_load( open( filepath ) )
|
|
187
|
-
self._x0 = y['x0']
|
|
188
|
-
self._y0 = y['y0']
|
|
189
|
-
self._oversamp = y['oversamp']
|
|
190
|
-
self._data = np.frombuffer( base64.b64decode( y['data'] ), dtype=y['dtype'] )
|
|
191
|
-
self._data = self._data.reshape( ( y['shape0'], y['shape1'] ) )
|
|
192
|
-
|
|
193
|
-
def write( self, filepath ):
|
|
194
|
-
out = { 'x0': float( self._x0 ),
|
|
195
|
-
'y0': float( self._y0 ),
|
|
196
|
-
'oversamp': self._oversamp,
|
|
197
|
-
'shape0': self._data.shape[0],
|
|
198
|
-
'shape1': self._data.shape[1],
|
|
199
|
-
'dtype': str( self._data.dtype ),
|
|
200
|
-
# TODO : make this right, think about endian-ness, etc.
|
|
201
|
-
'data': base64.b64encode( self._data.tobytes() ).decode( 'utf-8' ) }
|
|
202
|
-
# TODO : check overwriting etc.
|
|
203
|
-
yaml.dump( out, open( filepath, 'w' ) )
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
class galsimPSF( PSF ):
|
|
207
|
-
pass
|
|
File without changes
|
|
File without changes
|
{roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md
RENAMED
|
File without changes
|
{roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/.github/ISSUE_TEMPLATE/ISSUE_TEMPLATE.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/.github/workflows/run_snappl_tests.yml
RENAMED
|
File without changes
|
|
File without changes
|
{roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/.github/workflows/sub_package_update.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/roman_snpit_snappl.egg-info/not-zip-safe
RENAMED
|
File without changes
|
{roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/roman_snpit_snappl.egg-info/requires.txt
RENAMED
|
File without changes
|
{roman_snpit_snappl-0.3.0 → roman_snpit_snappl-0.5.0}/roman_snpit_snappl.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|