LSST Applications g0265f82a02+d6b5cd48b5,g02d81e74bb+a41d3748ce,g1470d8bcf6+6be6c9203b,g2079a07aa2+14824f138e,g212a7c68fe+a4f2ea4efa,g2305ad1205+72971fe858,g295015adf3+ab2c85acae,g2bbee38e9b+d6b5cd48b5,g337abbeb29+d6b5cd48b5,g3ddfee87b4+31b3a28dff,g487adcacf7+082e807817,g50ff169b8f+5929b3527e,g52b1c1532d+a6fc98d2e7,g591dd9f2cf+b2918d57ae,g5a732f18d5+66d966b544,g64a986408d+a41d3748ce,g858d7b2824+a41d3748ce,g8a8a8dda67+a6fc98d2e7,g99cad8db69+7fe4acdf18,g9ddcbc5298+d4bad12328,ga1e77700b3+246acaaf9c,ga8c6da7877+84af8b3ff8,gb0e22166c9+3863383f4c,gb6a65358fc+d6b5cd48b5,gba4ed39666+9664299f35,gbb8dafda3b+d8d527deb2,gc07e1c2157+b2dbe6b631,gc120e1dc64+61440b2abb,gc28159a63d+d6b5cd48b5,gcf0d15dbbd+31b3a28dff,gdaeeff99f8+a38ce5ea23,ge6526c86ff+39927bb362,ge79ae78c31+d6b5cd48b5,gee10cc3b42+a6fc98d2e7,gf1cff7945b+a41d3748ce,v24.1.5.rc1
LSST Data Management Base Package
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 "MockReferenceObjectLoaderFromMemory"]
27
28import numpy as np
29import esutil
30
31import lsst.geom
32import lsst.afw.image as afwImage
33from lsst.pipe.base import InMemoryDatasetHandle
34from lsst import sphgeom
35from . import SingleGaussianPsf
36from . import Defect
37
38from . import ReferenceObjectLoader
39import lsst.afw.table as afwTable
40
41
42def plantSources(bbox, kwid, sky, coordList, addPoissonNoise=True):
43 """Make an exposure with stars (modelled as Gaussians)
44
45 Parameters
46 ----------
47 bbox : `lsst.geom.Box2I`
48 Parent bbox of exposure
49 kwid : `int`
50 Kernal width (and height; kernal is square)
51 sky : `float`
52 Amount of sky background (counts)
53 coordList : `list [tuple]`
54 A list of [x, y, counts, sigma] where:
55 * x,y are relative to exposure origin
56 * counts is the integrated counts for the star
57 * sigma is the Gaussian sigma in pixels
58 addPoissonNoise : `bool`
59 If True: add Poisson noise to the exposure
60 """
61 # make an image with sources
62 img = afwImage.ImageD(bbox)
63 meanSigma = 0.0
64 for coord in coordList:
65 x, y, counts, sigma = coord
66 meanSigma += sigma
67
68 # make a single gaussian psf
69 psf = SingleGaussianPsf(kwid, kwid, sigma)
70
71 # make an image of it and scale to the desired number of counts
72 thisPsfImg = psf.computeImage(lsst.geom.PointD(x, y))
73 thisPsfImg *= counts
74
75 # bbox a window in our image and add the fake star image
76 psfBox = thisPsfImg.getBBox()
77 psfBox.clip(bbox)
78 if psfBox != thisPsfImg.getBBox():
79 thisPsfImg = thisPsfImg[psfBox, afwImage.PARENT]
80 imgSeg = img[psfBox, afwImage.PARENT]
81 imgSeg += thisPsfImg
82 meanSigma /= len(coordList)
83
84 img += sky
85
86 # add Poisson noise
87 if (addPoissonNoise):
88 np.random.seed(seed=1) # make results reproducible
89 imgArr = img.getArray()
90 imgArr[:] = np.random.poisson(imgArr)
91
92 # bundle into a maskedimage and an exposure
93 mask = afwImage.Mask(bbox)
94 var = img.convertFloat()
95 img -= sky
96 mimg = afwImage.MaskedImageF(img.convertFloat(), mask, var)
97 exposure = afwImage.makeExposure(mimg)
98
99 # insert an approximate psf
100 psf = SingleGaussianPsf(kwid, kwid, meanSigma)
101 exposure.setPsf(psf)
102
103 return exposure
104
105
106def makeRandomTransmissionCurve(rng, minWavelength=4000.0, maxWavelength=7000.0, nWavelengths=200,
107 maxRadius=80.0, nRadii=30, perturb=0.05):
108 """Create a random TransmissionCurve with nontrivial spatial and
109 wavelength variation.
110
111 Parameters
112 ----------
113 rng : numpy.random.RandomState
114 Random number generator.
115 minWavelength : float
116 Average minimum wavelength for generated TransmissionCurves (will be
117 randomly perturbed).
118 maxWavelength : float
119 Average maximum wavelength for generated TransmissionCurves (will be
120 randomly perturbed).
121 nWavelengths : int
122 Number of samples in the wavelength dimension.
123 maxRadius : float
124 Average maximum radius for spatial variation (will be perturbed).
125 nRadii : int
126 Number of samples in the radial dimension.
127 perturb: float
128 Fraction by which wavelength and radius bounds should be randomly
129 perturbed.
130 """
131 dWavelength = maxWavelength - minWavelength
132
133 def perturbed(x, s=perturb*dWavelength):
134 return x + 2.0*s*(rng.rand() - 0.5)
135
136 wavelengths = np.linspace(perturbed(minWavelength), perturbed(maxWavelength), nWavelengths)
137 radii = np.linspace(0.0, perturbed(maxRadius, perturb*maxRadius), nRadii)
138 throughput = np.zeros(wavelengths.shape + radii.shape, dtype=float)
139 # throughput will be a rectangle in wavelength, shifting to higher wavelengths and shrinking
140 # in height with radius, going to zero at all bounds.
141 peak0 = perturbed(0.9, 0.05)
142 start0 = perturbed(minWavelength + 0.25*dWavelength)
143 stop0 = perturbed(minWavelength + 0.75*dWavelength)
144 for i, r in enumerate(radii):
145 mask = np.logical_and(wavelengths >= start0 + r, wavelengths <= stop0 + r)
146 throughput[mask, i] = peak0*(1.0 - r/1000.0)
147 return afwImage.TransmissionCurve.makeRadial(throughput, wavelengths, radii)
148
149
151 """Create a list of defects that can be used for testing.
152
153 Returns
154 -------
155 defectList = `list` [`lsst.meas.algorithms.Defect`]
156 The list of defects.
157 """
158 defectList = [Defect(lsst.geom.Box2I(lsst.geom.Point2I(962, 0),
159 lsst.geom.Extent2I(2, 4611))),
160 Defect(lsst.geom.Box2I(lsst.geom.Point2I(1316, 0),
161 lsst.geom.Extent2I(2, 4611))),
162 Defect(lsst.geom.Box2I(lsst.geom.Point2I(1576, 0),
163 lsst.geom.Extent2I(4, 4611))),
164 Defect(lsst.geom.Box2I(lsst.geom.Point2I(1626, 0),
165 lsst.geom.Extent2I(2, 4611))),
166 Defect(lsst.geom.Box2I(lsst.geom.Point2I(1994, 252),
167 lsst.geom.Extent2I(2, 4359))),
168 Defect(lsst.geom.Box2I(lsst.geom.Point2I(1426, 702),
169 lsst.geom.Extent2I(2, 3909))),
170 Defect(lsst.geom.Box2I(lsst.geom.Point2I(1526, 1140),
171 lsst.geom.Extent2I(2, 3471))),
172 Defect(lsst.geom.Box2I(lsst.geom.Point2I(856, 2300),
173 lsst.geom.Extent2I(2, 2311))),
174 Defect(lsst.geom.Box2I(lsst.geom.Point2I(858, 2328),
175 lsst.geom.Extent2I(2, 65))),
176 Defect(lsst.geom.Box2I(lsst.geom.Point2I(859, 2328),
177 lsst.geom.Extent2I(1, 56))),
178 Defect(lsst.geom.Box2I(lsst.geom.Point2I(844, 2796),
179 lsst.geom.Extent2I(4, 1814))),
180 Defect(lsst.geom.Box2I(lsst.geom.Point2I(1366, 2804),
181 lsst.geom.Extent2I(2, 1806))),
182 Defect(lsst.geom.Box2I(lsst.geom.Point2I(1766, 3844),
183 lsst.geom.Extent2I(2, 766))),
184 Defect(lsst.geom.Box2I(lsst.geom.Point2I(1872, 4228),
185 lsst.geom.Extent2I(2, 382))),
186 ]
187
188 return defectList
189
190
192 """Mock reference catalog dataId.
193
194 The reference catalog dataId is only used to retrieve a region property.
195
196 Parameters
197 ----------
198 region : `lsst.sphgeom.Region`
199 The region associated with this mock dataId.
200 """
201 def __init__(self, region):
202 self._region = region
203
204 @property
205 def region(self):
206 return self._region
207
208
209class MockReferenceObjectLoaderFromFiles(ReferenceObjectLoader):
210 """A mock of ReferenceObjectLoader using files on disk.
211
212 This mock ReferenceObjectLoader uses a set of files on disk to create
213 mock dataIds and data reference handles that can be accessed
214 without a butler. The files must be afw catalog files in the reference
215 catalog format, sharded with HTM pixelization.
216
217 Parameters
218 ----------
219 filenames : `list` [`str`]
220 Names of files to use.
221 config : `lsst.meas.astrom.LoadReferenceObjectsConfig`, optional
222 Configuration object if necessary to override defaults.
223 htmLevel : `int`, optional
224 HTM level to use for the loader.
225 """
226 def __init__(self, filenames, name='cal_ref_cat', config=None, htmLevel=4):
227 dataIds, refCats = self._createDataIdsAndRefcats(filenames, htmLevel, name)
228
229 super().__init__(dataIds, refCats, name=name, config=config)
230
231 def _createDataIdsAndRefcats(self, filenames, htmLevel, name):
232 """Create mock dataIds and refcat handles.
233
234 Parameters
235 ----------
236 filenames : `list` [`str`]
237 Names of files to use.
238 htmLevel : `int`
239 HTM level to use for the loader.
240 name : `str`
241 Name of reference catalog (for logging).
242
243 Returns
244 -------
245 dataIds : `list` [`MockRefcatDataId`]
246 List of mock dataIds.
247 refCats : `list` [`lsst.pipe.base.InMemoryDatasetHandle`]
248 List of mock deferred dataset handles.
249
250 Raises
251 ------
252 RuntimeError if any file contains sources that cover more than one HTM
253 pixel at level ``htmLevel``.
254 """
255 pixelization = sphgeom.HtmPixelization(htmLevel)
256 htm = esutil.htm.HTM(htmLevel)
257
258 dataIds = []
259 refCats = []
260
261 for filename in filenames:
262 cat = afwTable.BaseCatalog.readFits(filename)
263
264 ids = htm.lookup_id(np.rad2deg(cat['coord_ra']), np.rad2deg(cat['coord_dec']))
265
266 if len(np.unique(ids)) != 1:
267 raise RuntimeError(f"File {filename} contains more than one pixel at level {htmLevel}")
268
269 dataIds.append(MockRefcatDataId(pixelization.pixel(ids[0])))
270 refCats.append(InMemoryDatasetHandle(cat, name=name))
271
272 return dataIds, refCats
273
274
275class MockReferenceObjectLoaderFromMemory(ReferenceObjectLoader):
276 """A mock of ReferenceObjectLoader using catalogs in memory.
277
278 Parameters
279 ----------
280 catalogs : `list` [`lsst.afw.table.SimpleCatalog`]
281 In-memory catalogs to use to mock dataIds.
282 config : `lsst.meas.astrom.LoadReferenceObjectsConfig`, optional
283 Configuration object if necessary to override defaults.
284 htmLevel : `int`, optional
285 HTM level to use for the loader.
286 """
287 def __init__(self, catalogs, name='mock_ref_cat', config=None, htmLevel=4):
288 dataIds, refCats = self._createDataIdsAndRefcats(catalogs, htmLevel, name)
289 super().__init__(dataIds, refCats, name=name, config=config)
290
291 def _createDataIdsAndRefcats(self, catalogs, htmLevel, name):
292 pixelization = sphgeom.HtmPixelization(htmLevel)
293 htm = esutil.htm.HTM(htmLevel)
294
295 dataIds = []
296 refCats = []
297
298 for i, catalog in enumerate(catalogs):
299 ids = htm.lookup_id(np.rad2deg(catalog['coord_ra']), np.rad2deg(catalog['coord_dec']))
300
301 if len(np.unique(ids)) != 1:
302 raise RuntimeError(f"Catalog number {i} contains more than one pixel at level {htmLevel}")
303
304 dataIds.append(MockRefcatDataId(pixelization.pixel(ids[0])))
305 refCats.append(InMemoryDatasetHandle(catalog, name=name))
306
307 return dataIds, refCats
Represent a 2-dimensional array of bitmask pixels.
Definition Mask.h:77
An integer coordinate rectangle.
Definition Box.h:55
__init__(self, filenames, name='cal_ref_cat', config=None, htmLevel=4)
Definition testUtils.py:226
__init__(self, catalogs, name='mock_ref_cat', config=None, htmLevel=4)
Definition testUtils.py:287
HtmPixelization provides HTM indexing of points and regions.
std::shared_ptr< Exposure< ImagePixelT, MaskPixelT, VariancePixelT > > makeExposure(MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > &mimage, std::shared_ptr< geom::SkyWcs const > wcs=std::shared_ptr< geom::SkyWcs const >())
A function to return an Exposure of the correct type (cf.
Definition Exposure.h:484
makeRandomTransmissionCurve(rng, minWavelength=4000.0, maxWavelength=7000.0, nWavelengths=200, maxRadius=80.0, nRadii=30, perturb=0.05)
Definition testUtils.py:107
plantSources(bbox, kwid, sky, coordList, addPoissonNoise=True)
Definition testUtils.py:42