Coverage for tests/surveyPropertyMapsTestUtils.py: 22%
87 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-02-09 12:17 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2023-02-09 12:17 +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().schema
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):
199 """Retrieve the specified dataset using the API of the Gen3 Butler.
201 Returns
202 -------
203 visit_summary : `lsst.afw.table.ExposureCatalog`
204 """
205 return self.visit_summary
208class MockCoaddReference(lsst.daf.butler.DeferredDatasetHandle):
209 """Very simple object that looks like a Gen3 data reference to
210 a coadd.
212 Parameters
213 ----------
214 exposure : `lsst.afw.image.Exposure`
215 The exposure to be retrieved by the data reference.
216 coaddName : `str`
217 The type of coadd produced. Typically "deep".
218 patch : `int`
219 Unique identifier for a subdivision of a tract.
220 tract : `int`
221 Unique identifier for a tract of a skyMap
222 """
223 def __init__(self, exposure, coaddName="deep", patch=0, tract=0):
224 self.coaddName = coaddName
225 self.exposure = exposure
226 self.tract = tract
227 self.patch = patch
229 def get(self, component=None):
230 """Retrieve the specified dataset using the API of the Gen 3 Butler.
232 Parameters
233 ----------
234 component : `str`, optional
235 If supplied, return the named metadata of the exposure. Allowed
236 components are "photoCalib" or "coaddInputs".
238 Returns
239 -------
240 `lsst.afw.image.Exposure` ('component=None') or
241 `lsst.afw.image.PhotoCalib` ('component="photoCalib") or
242 `lsst.afw.image.CoaddInputs` ('component="coaddInputs")
243 """
244 if component == "photoCalib":
245 return self.exposure.getPhotoCalib()
246 elif component == "coaddInputs":
247 return self.exposure.getInfo().getCoaddInputs()
249 return self.exposure.clone()
252class MockInputMapReference(lsst.daf.butler.DeferredDatasetHandle):
253 """Very simple object that looks like a Gen3 data reference to
254 an input map.
256 Parameters
257 ----------
258 input_map : `healsparse.HealSparseMap`
259 Bitwise input map.
260 patch : `int`
261 Patch number.
262 tract : `int`
263 Tract number.
264 """
265 def __init__(self, input_map, patch=0, tract=0):
266 self.input_map = input_map
267 self.tract = tract
268 self.patch = patch
270 def get(self):
271 """
272 Retrieve the specified dataset using the API of the Gen 3 Butler.
274 Returns
275 -------
276 input_map : `healsparse.HealSparseMap`
277 """
278 return self.input_map