LSST Applications g04a91732dc+01eac3be2a,g07dc498a13+80b84b0d75,g0fba68d861+93ae69f531,g1409bbee79+80b84b0d75,g1a7e361dbc+80b84b0d75,g1fd858c14a+62fce11e04,g21d47ad084+612e5f560a,g35bb328faa+fcb1d3bbc8,g42c1b31a95+a1301e4c20,g4e0f332c67+5d362be553,g51f2318141+336db69876,g53246c7159+fcb1d3bbc8,g60b5630c4e+199bb0f7b6,g78460c75b0+2f9a1b4bcd,g786e29fd12+cf7ec2a62a,g7b71ed6315+fcb1d3bbc8,g7c8978a71e+63142fe354,g8852436030+144acb3a42,g89139ef638+80b84b0d75,g8d6b6b353c+199bb0f7b6,g9125e01d80+fcb1d3bbc8,g989de1cb63+80b84b0d75,g9f33ca652e+3c0760037a,ga9baa6287d+199bb0f7b6,ga9e4eb89a6+52c894a0f6,gaaedd4e678+80b84b0d75,gabe3b4be73+1e0a283bba,gb1101e3267+748a15ef4d,gb58c049af0+f03b321e39,gb90eeb9370+4ac62396ab,gcf25f946ba+144acb3a42,gd315a588df+d7f700c4fc,gd6cbbdb0b4+c8606af20c,gd9a9a58781+fcb1d3bbc8,gde0f65d7ad+495d0ed6c5,ge278dab8ac+932305ba37,ge82c20c137+76d20ab76d,gf0ff2d8333+199bb0f7b6,w.2025.09
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 # Make results predictable over different numpy versions.
89 rng = np.random.Generator(np.random.MT19937(5))
90 imgArr = img.getArray()
91 imgArr[:] = rng.poisson(imgArr)
92
93 # bundle into a maskedimage and an exposure
94 mask = afwImage.Mask(bbox)
95 var = img.convertFloat()
96 img -= sky
97 mimg = afwImage.MaskedImageF(img.convertFloat(), mask, var)
98 exposure = afwImage.makeExposure(mimg)
99
100 # insert an approximate psf
101 psf = SingleGaussianPsf(kwid, kwid, meanSigma)
102 exposure.setPsf(psf)
103
104 return exposure
105
106
107def makeRandomTransmissionCurve(rng, minWavelength=4000.0, maxWavelength=7000.0, nWavelengths=200,
108 maxRadius=80.0, nRadii=30, perturb=0.05):
109 """Create a random TransmissionCurve with nontrivial spatial and
110 wavelength variation.
111
112 Parameters
113 ----------
114 rng : numpy.random.RandomState
115 Random number generator.
116 minWavelength : float
117 Average minimum wavelength for generated TransmissionCurves (will be
118 randomly perturbed).
119 maxWavelength : float
120 Average maximum wavelength for generated TransmissionCurves (will be
121 randomly perturbed).
122 nWavelengths : int
123 Number of samples in the wavelength dimension.
124 maxRadius : float
125 Average maximum radius for spatial variation (will be perturbed).
126 nRadii : int
127 Number of samples in the radial dimension.
128 perturb: float
129 Fraction by which wavelength and radius bounds should be randomly
130 perturbed.
131 """
132 dWavelength = maxWavelength - minWavelength
133
134 def perturbed(x, s=perturb*dWavelength):
135 return x + 2.0*s*(rng.rand() - 0.5)
136
137 wavelengths = np.linspace(perturbed(minWavelength), perturbed(maxWavelength), nWavelengths)
138 radii = np.linspace(0.0, perturbed(maxRadius, perturb*maxRadius), nRadii)
139 throughput = np.zeros(wavelengths.shape + radii.shape, dtype=float)
140 # throughput will be a rectangle in wavelength, shifting to higher wavelengths and shrinking
141 # in height with radius, going to zero at all bounds.
142 peak0 = perturbed(0.9, 0.05)
143 start0 = perturbed(minWavelength + 0.25*dWavelength)
144 stop0 = perturbed(minWavelength + 0.75*dWavelength)
145 for i, r in enumerate(radii):
146 mask = np.logical_and(wavelengths >= start0 + r, wavelengths <= stop0 + r)
147 throughput[mask, i] = peak0*(1.0 - r/1000.0)
148 return afwImage.TransmissionCurve.makeRadial(throughput, wavelengths, radii)
149
150
152 """Create a list of defects that can be used for testing.
153
154 Returns
155 -------
156 defectList = `list` [`lsst.meas.algorithms.Defect`]
157 The list of defects.
158 """
159 defectList = [Defect(lsst.geom.Box2I(lsst.geom.Point2I(962, 0),
160 lsst.geom.Extent2I(2, 4611))),
162 lsst.geom.Extent2I(2, 4611))),
164 lsst.geom.Extent2I(4, 4611))),
166 lsst.geom.Extent2I(2, 4611))),
168 lsst.geom.Extent2I(2, 4359))),
170 lsst.geom.Extent2I(2, 3909))),
172 lsst.geom.Extent2I(2, 3471))),
174 lsst.geom.Extent2I(2, 2311))),
176 lsst.geom.Extent2I(2, 65))),
178 lsst.geom.Extent2I(1, 56))),
180 lsst.geom.Extent2I(4, 1814))),
182 lsst.geom.Extent2I(2, 1806))),
184 lsst.geom.Extent2I(2, 766))),
186 lsst.geom.Extent2I(2, 382))),
187 ]
188
189 return defectList
190
191
193 """Mock reference catalog dataId.
194
195 The reference catalog dataId is only used to retrieve a region property.
196
197 Parameters
198 ----------
199 region : `lsst.sphgeom.Region`
200 The region associated with this mock dataId.
201 """
202 def __init__(self, region):
203 self._region = region
204
205 @property
206 def region(self):
207 return self._region
208
209
211 """A mock of ReferenceObjectLoader using files on disk.
212
213 This mock ReferenceObjectLoader uses a set of files on disk to create
214 mock dataIds and data reference handles that can be accessed
215 without a butler. The files must be afw catalog files in the reference
216 catalog format, sharded with HTM pixelization.
217
218 Parameters
219 ----------
220 filenames : `list` [`str`]
221 Names of files to use.
222 config : `lsst.meas.astrom.LoadReferenceObjectsConfig`, optional
223 Configuration object if necessary to override defaults.
224 htmLevel : `int`, optional
225 HTM level to use for the loader.
226 """
227 def __init__(self, filenames, name='cal_ref_cat', config=None, htmLevel=4):
228 dataIds, refCats = self._createDataIdsAndRefcats(filenames, htmLevel, name)
229
230 super().__init__(dataIds, refCats, name=name, config=config)
231
232 def _createDataIdsAndRefcats(self, filenames, htmLevel, name):
233 """Create mock dataIds and refcat handles.
234
235 Parameters
236 ----------
237 filenames : `list` [`str`]
238 Names of files to use.
239 htmLevel : `int`
240 HTM level to use for the loader.
241 name : `str`
242 Name of reference catalog (for logging).
243
244 Returns
245 -------
246 dataIds : `list` [`MockRefcatDataId`]
247 List of mock dataIds.
248 refCats : `list` [`lsst.pipe.base.InMemoryDatasetHandle`]
249 List of mock deferred dataset handles.
250
251 Raises
252 ------
253 RuntimeError if any file contains sources that cover more than one HTM
254 pixel at level ``htmLevel``.
255 """
256 pixelization = sphgeom.HtmPixelization(htmLevel)
257 htm = esutil.htm.HTM(htmLevel)
258
259 dataIds = []
260 refCats = []
261
262 for filename in filenames:
263 cat = afwTable.BaseCatalog.readFits(filename)
264
265 ids = htm.lookup_id(np.rad2deg(cat['coord_ra']), np.rad2deg(cat['coord_dec']))
266
267 if len(np.unique(ids)) != 1:
268 raise RuntimeError(f"File {filename} contains more than one pixel at level {htmLevel}")
269
270 dataIds.append(MockRefcatDataId(pixelization.pixel(ids[0])))
271 refCats.append(InMemoryDatasetHandle(cat, name=name))
272
273 return dataIds, refCats
274
275
277 """A mock of ReferenceObjectLoader using catalogs in memory.
278
279 Parameters
280 ----------
281 catalogs : `list` [`lsst.afw.table.SimpleCatalog`]
282 In-memory catalogs to use to mock dataIds.
283 config : `lsst.meas.astrom.LoadReferenceObjectsConfig`, optional
284 Configuration object if necessary to override defaults.
285 htmLevel : `int`, optional
286 HTM level to use for the loader.
287 """
288 def __init__(self, catalogs, name='mock_ref_cat', config=None, htmLevel=4):
289 dataIds, refCats = self._createDataIdsAndRefcats(catalogs, htmLevel, name)
290 super().__init__(dataIds, refCats, name=name, config=config)
291
292 def _createDataIdsAndRefcats(self, catalogs, htmLevel, name):
293 pixelization = sphgeom.HtmPixelization(htmLevel)
294 htm = esutil.htm.HTM(htmLevel)
295
296 dataIds = []
297 refCats = []
298
299 for i, catalog in enumerate(catalogs):
300 ids = htm.lookup_id(np.rad2deg(catalog['coord_ra']), np.rad2deg(catalog['coord_dec']))
301
302 if len(np.unique(ids)) != 1:
303 raise RuntimeError(f"Catalog number {i} contains more than one pixel at level {htmLevel}")
304
305 dataIds.append(MockRefcatDataId(pixelization.pixel(ids[0])))
306 refCats.append(InMemoryDatasetHandle(catalog, name=name))
307
308 return dataIds, refCats
Represent a 2-dimensional array of bitmask pixels.
Definition Mask.h:82
An integer coordinate rectangle.
Definition Box.h:55
Encapsulate information about a bad portion of a detector.
Definition Interp.h:72
Represent a PSF as a circularly symmetrical Gaussian.
__init__(self, filenames, name='cal_ref_cat', config=None, htmLevel=4)
Definition testUtils.py:227
__init__(self, catalogs, name='mock_ref_cat', config=None, htmLevel=4)
Definition testUtils.py:288
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:108
plantSources(bbox, kwid, sky, coordList, addPoissonNoise=True)
Definition testUtils.py:42