Coverage for tests/surveyPropertyMapsTestUtils.py: 26%
87 statements
« prev ^ index » next coverage.py v6.4.4, created at 2022-08-18 20:10 +0000
« prev ^ index » next coverage.py v6.4.4, created at 2022-08-18 20:10 +0000
1# This file is part of pipe_tasks.
2#
3# LSST Data Management System
4# This product includes software developed by the
5# LSST Project (http://www.lsst.org/).
6# See COPYRIGHT file at the top of the source tree.
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the LSST License Statement and
19# the GNU General Public License along with this program. If not,
20# see <https://www.lsstcorp.org/LegalNotices/>.
21#
22"""Utilities for HealSparsePropertyMapTask and others."""
23import numpy as np
25import lsst.daf.butler
26import lsst.geom as geom
27from lsst.daf.base import DateTime
28from lsst.afw.coord import Observatory
29from lsst.pipe.tasks.postprocess import ConsolidateVisitSummaryTask
30import lsst.afw.table as afwTable
31import lsst.afw.image as afwImage
32import lsst.afw.geom as afwGeom
33from lsst.afw.detection import GaussianPsf
36__all__ = ['makeMockVisitSummary', 'MockVisitSummaryReference', 'MockCoaddReference',
37 'MockInputMapReference']
40def makeMockVisitSummary(visit,
41 ra_center=0.0,
42 dec_center=-45.0,
43 physical_filter='TEST-I',
44 band='i',
45 mjd=59234.7083333334,
46 psf_sigma=3.0,
47 zenith_distance=45.0,
48 zero_point=30.0,
49 sky_background=100.0,
50 sky_noise=10.0,
51 mean_var=100.0,
52 exposure_time=100.0,
53 detector_size=200,
54 pixel_scale=0.2):
55 """Make a mock visit summary catalog.
57 This will contain two square detectors with the same metadata,
58 with a small (20 pixel) gap between the detectors. There is no
59 rotation, as each detector is simply offset in RA from the
60 specified boresight.
62 Parameters
63 ----------
64 visit : `int`
65 Visit number.
66 ra_center : `float`
67 Right ascension of the center of the "camera" boresight (degrees).
68 dec_center : `float`
69 Declination of the center of the "camera" boresight (degrees).
70 physical_filter : `str`
71 Arbitrary name for the physical filter.
72 band : `str`
73 Name of the associated band.
74 mjd : `float`
75 Modified Julian Date.
76 psf_sigma : `float`
77 Sigma width of Gaussian psf.
78 zenith_distance : `float`
79 Distance from zenith of the visit (degrees).
80 zero_point : `float`
81 Constant zero point for the visit (magnitudes).
82 sky_background : `float`
83 Background level for the visit (counts).
84 sky_noise : `float`
85 Noise level for the background of the visit (counts).
86 mean_var : `float`
87 Mean of the variance plane of the visit (counts).
88 exposure_time : `float`
89 Exposure time of the visit (seconds).
90 detector_size : `int`
91 Size of each square detector in the visit (pixels).
92 pixel_scale : `float`
93 Size of the pixel in arcseconds per pixel.
95 Returns
96 -------
97 visit_summary : `lsst.afw.table.ExposureCatalog`
98 """
99 # We are making a 2 detector "camera"
100 n_detector = 2
102 schema = ConsolidateVisitSummaryTask()._makeVisitSummarySchema()
103 visit_summary = afwTable.ExposureCatalog(schema)
104 visit_summary.resize(n_detector)
106 bbox = geom.Box2I(x=geom.IntervalI(min=0, max=detector_size - 1),
107 y=geom.IntervalI(min=0, max=detector_size - 1))
109 for detector_id in range(n_detector):
110 row = visit_summary[detector_id]
112 row['id'] = detector_id
113 row.setBBox(bbox)
114 row['visit'] = visit
115 row['physical_filter'] = physical_filter
116 row['band'] = band
117 row['zenithDistance'] = zenith_distance
118 row['zeroPoint'] = zero_point
119 row['skyBg'] = sky_background
120 row['skyNoise'] = sky_noise
121 row['meanVar'] = mean_var
123 # Generate a photocalib
124 instFluxMag0 = 10.**(zero_point/2.5)
125 row.setPhotoCalib(afwImage.makePhotoCalibFromCalibZeroPoint(instFluxMag0))
127 # Generate a WCS and set values accordingly
128 crpix = geom.Point2D(detector_size/2., detector_size/2.)
129 # Create a 20 pixel gap between the two detectors (each offset 10 pixels).
130 if detector_id == 0:
131 delta_ra = -1.0*((detector_size + 10)*pixel_scale/3600.)/np.cos(np.deg2rad(dec_center))
132 delta_dec = 0.0
133 elif detector_id == 1:
134 delta_ra = ((detector_size + 10)*pixel_scale/3600.)/np.cos(np.deg2rad(dec_center))
135 delta_dec = 0.0
136 crval = geom.SpherePoint(ra_center + delta_ra, dec_center + delta_dec, geom.degrees)
137 cd_matrix = afwGeom.makeCdMatrix(scale=pixel_scale*geom.arcseconds, orientation=0.0*geom.degrees)
138 wcs = afwGeom.makeSkyWcs(crpix=crpix, crval=crval, cdMatrix=cd_matrix)
139 row.setWcs(wcs)
141 sph_pts = wcs.pixelToSky(geom.Box2D(bbox).getCorners())
142 row['raCorners'] = np.array([float(sph.getRa().asDegrees()) for sph in sph_pts])
143 row['decCorners'] = np.array([float(sph.getDec().asDegrees()) for sph in sph_pts])
144 sph_pt = wcs.pixelToSky(bbox.getCenter())
145 row['ra'] = sph_pt.getRa().asDegrees()
146 row['decl'] = sph_pt.getDec().asDegrees()
148 # Generate a visitInfo.
149 # This does not need to be consistent with the zenith angle in the table,
150 # it just needs to be valid and have sufficient information to compute
151 # exposure time and parallactic angle.
152 date = DateTime(date=mjd, system=DateTime.DateSystem.MJD)
153 visit_info = afwImage.VisitInfo(exposureId=visit,
154 exposureTime=exposure_time,
155 date=date,
156 darkTime=0.0,
157 boresightRaDec=geom.SpherePoint(ra_center,
158 dec_center,
159 geom.degrees),
160 era=45.1*geom.degrees,
161 observatory=Observatory(
162 11.1*geom.degrees,
163 0.0*geom.degrees,
164 0.333),
165 boresightRotAngle=0.0*geom.degrees,
166 rotType=afwImage.RotType.SKY)
167 row.setVisitInfo(visit_info)
169 # Generate a PSF and set values accordingly
170 psf = GaussianPsf(15, 15, psf_sigma)
171 row.setPsf(psf)
172 psfAvgPos = psf.getAveragePosition()
173 shape = psf.computeShape(psfAvgPos)
174 row['psfSigma'] = psf.getSigma()
175 row['psfIxx'] = shape.getIxx()
176 row['psfIyy'] = shape.getIyy()
177 row['psfIxy'] = shape.getIxy()
178 row['psfArea'] = shape.getArea()
180 return visit_summary
183class MockVisitSummaryReference(lsst.daf.butler.DeferredDatasetHandle):
184 """Very simple object that looks like a Gen3 data reference to
185 a visit summary.
187 Parameters
188 ----------
189 visit_summary : `lsst.afw.table.ExposureCatalog`
190 Visit summary catalog.
191 visit : `int`
192 Visit number.
193 """
194 def __init__(self, visit_summary, visit):
195 self.visit_summary = visit_summary
196 self.visit = visit
198 def get(self, **kwargs):
199 """Retrieve the specified dataset using the API of the Gen3 Butler.
201 Parameters
202 ----------
203 **kwargs :
204 Additional keyword arguments such as `immediate=True` that would
205 control internal butler behavior.
207 Returns
208 -------
209 visit_summary : `lsst.afw.table.ExposureCatalog`
210 """
211 return self.visit_summary
214class MockCoaddReference(lsst.daf.butler.DeferredDatasetHandle):
215 """Very simple object that looks like a Gen3 data reference to
216 a coadd.
218 Parameters
219 ----------
220 exposure : `lsst.afw.image.Exposure`
221 The exposure to be retrieved by the data reference.
222 coaddName : `str`
223 The type of coadd produced. Typically "deep".
224 patch : `int`
225 Unique identifier for a subdivision of a tract.
226 tract : `int`
227 Unique identifier for a tract of a skyMap
228 """
229 def __init__(self, exposure, coaddName="deep", patch=0, tract=0):
230 self.coaddName = coaddName
231 self.exposure = exposure
232 self.tract = tract
233 self.patch = patch
235 def get(self, component=None, **kwargs):
236 """Retrieve the specified dataset using the API of the Gen 3 Butler.
238 Parameters
239 ----------
240 component : `str`, optional
241 If supplied, return the named metadata of the exposure. Allowed
242 components are "photoCalib" or "coaddInputs".
243 **kwargs
244 Additional keyword arguments such as `immediate=True` that would
245 control internal butler behavior.
247 Returns
248 -------
249 `lsst.afw.image.Exposure` ('component=None') or
250 `lsst.afw.image.PhotoCalib` ('component="photoCalib") or
251 `lsst.afw.image.CoaddInputs` ('component="coaddInputs")
252 """
253 if component == "photoCalib":
254 return self.exposure.getPhotoCalib()
255 elif component == "coaddInputs":
256 return self.exposure.getInfo().getCoaddInputs()
258 return self.exposure.clone()
261class MockInputMapReference(lsst.daf.butler.DeferredDatasetHandle):
262 """Very simple object that looks like a Gen3 data reference to
263 an input map.
265 Parameters
266 ----------
267 input_map : `healsparse.HealSparseMap`
268 Bitwise input map.
269 patch : `int`
270 Patch number.
271 tract : `int`
272 Tract number.
273 """
274 def __init__(self, input_map, patch=0, tract=0):
275 self.input_map = input_map
276 self.tract = tract
277 self.patch = patch
279 def get(self, **kwargs):
280 """
281 Retrieve the specified dataset using the API of the Gen 3 Butler.
283 Parameters
284 ----------
285 **kwargs
286 Additional keyword arguments such as `immediate=True` that would
287 control internal butler behavior.
289 Returns
290 -------
291 input_map : `healsparse.HealSparseMap`
292 """
293 return self.input_map