lsst.meas.algorithms gf28172b03b+9cf95247d4
Loading...
Searching...
No Matches
testUtils.py
Go to the documentation of this file.
2# LSST Data Management System
3#
4# Copyright 2008-2017 AURA/LSST.
5#
6# This product includes software developed by the
7# LSST Project (http://www.lsst.org/).
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the LSST License Statement and
20# the GNU General Public License along with this program. If not,
21# see <https://www.lsstcorp.org/LegalNotices/>.
22#
23
24__all__ = ["plantSources", "makeRandomTransmissionCurve", "makeDefectList",
25 "MockReferenceObjectLoaderFromFiles", "MockRefcatDataId"]
26
27import numpy as np
28import esutil
29
30import lsst.geom
31import lsst.afw.image as afwImage
32from lsst.pipe.base import InMemoryDatasetHandle
33from lsst import sphgeom
34from . import SingleGaussianPsf
35from . import Defect
36
37from . import ReferenceObjectLoader
38import lsst.afw.table as afwTable
39
40
41def plantSources(bbox, kwid, sky, coordList, addPoissonNoise=True):
42 """Make an exposure with stars (modelled as Gaussians)
43
44 Parameters
45 ----------
46 bbox : `lsst.geom.Box2I`
47 Parent bbox of exposure
48 kwid : `int`
49 Kernal width (and height; kernal is square)
50 sky : `float`
51 Amount of sky background (counts)
52 coordList : `list [tuple]`
53 A list of [x, y, counts, sigma] where:
54 * x,y are relative to exposure origin
55 * counts is the integrated counts for the star
56 * sigma is the Gaussian sigma in pixels
57 addPoissonNoise : `bool`
58 If True: add Poisson noise to the exposure
59 """
60 # make an image with sources
61 img = afwImage.ImageD(bbox)
62 meanSigma = 0.0
63 for coord in coordList:
64 x, y, counts, sigma = coord
65 meanSigma += sigma
66
67 # make a single gaussian psf
68 psf = SingleGaussianPsf(kwid, kwid, sigma)
69
70 # make an image of it and scale to the desired number of counts
71 thisPsfImg = psf.computeImage(lsst.geom.PointD(x, y))
72 thisPsfImg *= counts
73
74 # bbox a window in our image and add the fake star image
75 psfBox = thisPsfImg.getBBox()
76 psfBox.clip(bbox)
77 if psfBox != thisPsfImg.getBBox():
78 thisPsfImg = thisPsfImg[psfBox, afwImage.PARENT]
79 imgSeg = img[psfBox, afwImage.PARENT]
80 imgSeg += thisPsfImg
81 meanSigma /= len(coordList)
82
83 img += sky
84
85 # add Poisson noise
86 if (addPoissonNoise):
87 np.random.seed(seed=1) # make results reproducible
88 imgArr = img.getArray()
89 imgArr[:] = np.random.poisson(imgArr)
90
91 # bundle into a maskedimage and an exposure
92 mask = afwImage.Mask(bbox)
93 var = img.convertFloat()
94 img -= sky
95 mimg = afwImage.MaskedImageF(img.convertFloat(), mask, var)
96 exposure = afwImage.makeExposure(mimg)
97
98 # insert an approximate psf
99 psf = SingleGaussianPsf(kwid, kwid, meanSigma)
100 exposure.setPsf(psf)
101
102 return exposure
103
104
105def makeRandomTransmissionCurve(rng, minWavelength=4000.0, maxWavelength=7000.0, nWavelengths=200,
106 maxRadius=80.0, nRadii=30, perturb=0.05):
107 """Create a random TransmissionCurve with nontrivial spatial and
108 wavelength variation.
109
110 Parameters
111 ----------
112 rng : numpy.random.RandomState
113 Random number generator.
114 minWavelength : float
115 Average minimum wavelength for generated TransmissionCurves (will be
116 randomly perturbed).
117 maxWavelength : float
118 Average maximum wavelength for generated TransmissionCurves (will be
119 randomly perturbed).
120 nWavelengths : int
121 Number of samples in the wavelength dimension.
122 maxRadius : float
123 Average maximum radius for spatial variation (will be perturbed).
124 nRadii : int
125 Number of samples in the radial dimension.
126 perturb: float
127 Fraction by which wavelength and radius bounds should be randomly
128 perturbed.
129 """
130 dWavelength = maxWavelength - minWavelength
131
132 def perturbed(x, s=perturb*dWavelength):
133 return x + 2.0*s*(rng.rand() - 0.5)
134
135 wavelengths = np.linspace(perturbed(minWavelength), perturbed(maxWavelength), nWavelengths)
136 radii = np.linspace(0.0, perturbed(maxRadius, perturb*maxRadius), nRadii)
137 throughput = np.zeros(wavelengths.shape + radii.shape, dtype=float)
138 # throughput will be a rectangle in wavelength, shifting to higher wavelengths and shrinking
139 # in height with radius, going to zero at all bounds.
140 peak0 = perturbed(0.9, 0.05)
141 start0 = perturbed(minWavelength + 0.25*dWavelength)
142 stop0 = perturbed(minWavelength + 0.75*dWavelength)
143 for i, r in enumerate(radii):
144 mask = np.logical_and(wavelengths >= start0 + r, wavelengths <= stop0 + r)
145 throughput[mask, i] = peak0*(1.0 - r/1000.0)
146 return afwImage.TransmissionCurve.makeRadial(throughput, wavelengths, radii)
147
148
150 """Create a list of defects that can be used for testing.
151
152 Returns
153 -------
154 defectList = `list` [`lsst.meas.algorithms.Defect`]
155 The list of defects.
156 """
157 defectList = [Defect(lsst.geom.Box2I(lsst.geom.Point2I(962, 0),
158 lsst.geom.Extent2I(2, 4611))),
159 Defect(lsst.geom.Box2I(lsst.geom.Point2I(1316, 0),
160 lsst.geom.Extent2I(2, 4611))),
161 Defect(lsst.geom.Box2I(lsst.geom.Point2I(1576, 0),
162 lsst.geom.Extent2I(4, 4611))),
163 Defect(lsst.geom.Box2I(lsst.geom.Point2I(1626, 0),
164 lsst.geom.Extent2I(2, 4611))),
165 Defect(lsst.geom.Box2I(lsst.geom.Point2I(1994, 252),
166 lsst.geom.Extent2I(2, 4359))),
167 Defect(lsst.geom.Box2I(lsst.geom.Point2I(1426, 702),
168 lsst.geom.Extent2I(2, 3909))),
169 Defect(lsst.geom.Box2I(lsst.geom.Point2I(1526, 1140),
170 lsst.geom.Extent2I(2, 3471))),
171 Defect(lsst.geom.Box2I(lsst.geom.Point2I(856, 2300),
172 lsst.geom.Extent2I(2, 2311))),
173 Defect(lsst.geom.Box2I(lsst.geom.Point2I(858, 2328),
174 lsst.geom.Extent2I(2, 65))),
175 Defect(lsst.geom.Box2I(lsst.geom.Point2I(859, 2328),
176 lsst.geom.Extent2I(1, 56))),
177 Defect(lsst.geom.Box2I(lsst.geom.Point2I(844, 2796),
178 lsst.geom.Extent2I(4, 1814))),
179 Defect(lsst.geom.Box2I(lsst.geom.Point2I(1366, 2804),
180 lsst.geom.Extent2I(2, 1806))),
181 Defect(lsst.geom.Box2I(lsst.geom.Point2I(1766, 3844),
182 lsst.geom.Extent2I(2, 766))),
183 Defect(lsst.geom.Box2I(lsst.geom.Point2I(1872, 4228),
184 lsst.geom.Extent2I(2, 382))),
185 ]
186
187 return defectList
188
189
191 """Mock reference catalog dataId.
192
193 The reference catalog dataId is only used to retrieve a region property.
194
195 Parameters
196 ----------
197 region : `lsst.sphgeom.Region`
198 The region associated with this mock dataId.
199 """
200 def __init__(self, region):
201 self._region = region
202
203 @property
204 def region(self):
205 return self._region
206
207
208class MockReferenceObjectLoaderFromFiles(ReferenceObjectLoader):
209 """A simple mock of ReferenceObjectLoader.
210
211 This mock ReferenceObjectLoader uses a set of files on disk to create
212 mock dataIds and data reference handles that can be accessed
213 without a butler. The files must be afw catalog files in the reference
214 catalog format, sharded with HTM pixelization.
215
216 Parameters
217 ----------
218 filenames : `list` [`str`]
219 Names of files to use.
220 config : `lsst.meas.astrom.LoadReferenceObjectsConfig`, optional
221 Configuration object if necessary to override defaults.
222 htmLevel : `int`, optional
223 HTM level to use for the loader.
224 """
225 def __init__(self, filenames, name='cal_ref_cat', config=None, htmLevel=4):
226 dataIds, refCats = self._createDataIdsAndRefcats(filenames, htmLevel, name)
227
228 super().__init__(dataIds, refCats, name=name, config=config)
229
230 def _createDataIdsAndRefcats(self, filenames, htmLevel, name):
231 """Create mock dataIds and refcat handles.
232
233 Parameters
234 ----------
235 filenames : `list` [`str`]
236 Names of files to use.
237 htmLevel : `int`
238 HTM level to use for the loader.
239 name : `str`
240 Name of reference catalog (for logging).
241
242 Returns
243 -------
244 dataIds : `list` [`MockRefcatDataId`]
245 List of mock dataIds.
246 refCats : `list` [`lsst.pipe.base.InMemoryDatasetHandle`]
247 List of mock deferred dataset handles.
248
249 Raises
250 ------
251 RuntimeError if any file contains sources that cover more than one HTM
252 pixel at level ``htmLevel``.
253 """
254 pixelization = sphgeom.HtmPixelization(htmLevel)
255 htm = esutil.htm.HTM(htmLevel)
256
257 dataIds = []
258 refCats = []
259
260 for filename in filenames:
261 cat = afwTable.BaseCatalog.readFits(filename)
262
263 ids = htm.lookup_id(np.rad2deg(cat['coord_ra']), np.rad2deg(cat['coord_dec']))
264
265 if len(np.unique(ids)) != 1:
266 raise RuntimeError(f"File {filename} contains more than one pixel at level {htmLevel}")
267
268 dataIds.append(MockRefcatDataId(pixelization.pixel(ids[0])))
269 refCats.append(InMemoryDatasetHandle(cat, name=name))
270
271 return dataIds, refCats
Encapsulate information about a bad portion of a detector.
Definition: Interp.h:72
def _createDataIdsAndRefcats(self, filenames, htmLevel, name)
Definition: testUtils.py:230
def __init__(self, filenames, name='cal_ref_cat', config=None, htmLevel=4)
Definition: testUtils.py:225
def plantSources(bbox, kwid, sky, coordList, addPoissonNoise=True)
Definition: testUtils.py:41
def makeRandomTransmissionCurve(rng, minWavelength=4000.0, maxWavelength=7000.0, nWavelengths=200, maxRadius=80.0, nRadii=30, perturb=0.05)
Definition: testUtils.py:106