23 from __future__
import absolute_import, division, print_function
24 from builtins
import range
27 import lsst.pex.config
30 from lsst.afw.cameraGeom
import PIXELS, FOCAL_PLANE
33 import lsst.afw.detection
35 from lsst.meas.base.apCorrRegistry
import getApCorrNameSet
39 pixelScale = lsst.pex.config.Field(
40 dtype=float, default=0.2, optional=
False,
41 doc=
"Pixel scale for mock WCSs in arcseconds/pixel" 43 doRotate = lsst.pex.config.Field(
44 dtype=bool, default=
True, optional=
False,
45 doc=
"Whether to randomly rotate observations relative to the tract Wcs" 47 fluxMag0 = lsst.pex.config.Field(
48 dtype=float, default=1E11, optional=
False,
49 doc=
"Flux at zero magnitude used to define Calibs." 51 fluxMag0Sigma = lsst.pex.config.Field(
52 dtype=float, default=100.0, optional=
False,
53 doc=
"Error on flux at zero magnitude used to define Calibs; used to add scatter as well." 55 expTime = lsst.pex.config.Field(
56 dtype=float, default=60.0, optional=
False,
57 doc=
"Exposure time set in generated Calibs (does not affect flux or noise level)" 59 psfImageSize = lsst.pex.config.Field(
60 dtype=int, default=21, optional=
False,
61 doc=
"Image width and height of generated Psfs." 63 psfMinSigma = lsst.pex.config.Field(
64 dtype=float, default=1.5, optional=
False,
65 doc=
"Minimum radius for generated Psfs." 67 psfMaxSigma = lsst.pex.config.Field(
68 dtype=float, default=3.0, optional=
False,
69 doc=
"Maximum radius for generated Psfs." 71 apCorrOrder = lsst.pex.config.Field(
72 dtype=int, default=1, optional=
False,
73 doc=
"Polynomial order for aperture correction fields" 75 seed = lsst.pex.config.Field(dtype=int, default=1, doc=
"Seed for numpy random number generator")
79 """Task to generate mock Exposure parameters (Wcs, Psf, Calib), intended for use as a subtask 83 - document "pa" in detail; angle of what to what? 84 - document the catalog parameter of the run method 87 ConfigClass = MockObservationConfig
90 lsst.pipe.base.Task.__init__(self, **kwds)
91 self.
schema = lsst.afw.table.ExposureTable.makeMinimalSchema()
92 self.
ccdKey = self.
schema.addField(
"ccd", type=np.int32, doc=
"CCD number")
93 self.
visitKey = self.
schema.addField(
"visit", type=np.int32, doc=
"visit number")
94 self.
pointingKey = lsst.afw.table.CoordKey.addFields(self.
schema,
"pointing",
"center of visit")
95 self.
rng = np.random.RandomState(self.config.seed)
97 def run(self, butler, n, tractInfo, camera, catalog=None):
98 """Driver that generates an ExposureCatalog of mock observations. 100 @param[in] butler: a data butler 101 @param[in] n: number of pointings 102 @param[in] camera: camera geometry (an lsst.afw.cameraGeom.Camera) 103 @param[in] catalog: catalog to which to add observations (an ExposureCatalog); 104 if None then a new catalog is created. 106 @todo figure out what `pa` is and use that knowledge to set `boresightRotAng` and `rotType` 109 catalog = lsst.afw.table.ExposureCatalog(self.
schema)
111 if not catalog.getSchema().contains(self.
schema):
112 raise ValueError(
"Catalog schema does not match Task schema")
116 visitInfo = lsst.afw.image.VisitInfo(
117 exposureTime = self.config.expTime,
118 date = lsst.daf.base.DateTime.now(),
119 boresightRaDec = position,
121 for detector
in camera:
123 record = catalog.addNew()
124 record.setI(self.
ccdKey, detector.getId())
127 record.setWcs(self.
buildWcs(position, pa, detector))
128 record.setCalib(calib)
129 record.setVisitInfo(visitInfo)
130 record.setPsf(self.
buildPsf(detector))
132 record.setBBox(detector.getBBox())
133 detectorId = detector.getId()
134 obj = butler.get(
"ccdExposureId", visit=visit, ccd=detectorId, immediate=
True)
140 """Generate (celestial) positions and rotation angles that define field locations. 142 Default implementation draws random pointings that are uniform in the tract's image 145 @param[in] n: number of pointings 146 @param[in] tractInfo: skymap tract (a lsst.skymap.TractInfo) 147 @return a Python iterable over (coord, angle) pairs: 148 - coord is an object position (an lsst.afw.coord.Coord) 149 - angle is a position angle (???) (an lsst.afw.geom.Angle) 151 The default implementation returns an iterator (i.e. the function is a "generator"), 152 but derived-class overrides may return any iterable. 154 wcs = tractInfo.getWcs()
155 bbox = lsst.afw.geom.Box2D(tractInfo.getBBox())
156 bbox.grow(lsst.afw.geom.Extent2D(-0.1 * bbox.getWidth(), -0.1 * bbox.getHeight()))
158 x = self.
rng.rand() * bbox.getWidth() + bbox.getMinX()
159 y = self.
rng.rand() * bbox.getHeight() + bbox.getMinY()
160 pa = 0.0 * lsst.afw.geom.radians
161 if self.config.doRotate:
162 pa = self.
rng.rand() * 2.0 * np.pi * lsst.afw.geom.radians
163 yield wcs.pixelToSky(x, y), pa
166 """Build a simple TAN Wcs with no distortion and exactly-aligned CCDs. 168 @param[in] position: object position on sky (an lsst.afw.coord.Coord) 169 @param[in] pa: position angle (an lsst.afw.geom.Angle) 170 @param[in] detector: detector information (an lsst.afw.cameraGeom.Detector) 173 pixelScale = (self.config.pixelScale * lsst.afw.geom.arcseconds).asDegrees()
174 cd = (lsst.afw.geom.LinearTransform.makeScaling(pixelScale) *
175 lsst.afw.geom.LinearTransform.makeRotation(pa))
176 fpCtr = detector.makeCameraPoint(lsst.afw.geom.Point2D(0, 0), FOCAL_PLANE)
177 crpix = detector.transform(fpCtr, PIXELS).getPoint()
179 wcs = lsst.afw.image.makeWcs(crval, crpix, *cd.getMatrix().flatten())
183 """Build a simple Calib object with exposure time fixed by config, fluxMag0 drawn from 184 a Gaussian defined by config, and mid-time set to DateTime.now(). 186 calib = lsst.afw.image.Calib()
188 self.
rng.randn() * self.config.fluxMag0Sigma + self.config.fluxMag0,
189 self.config.fluxMag0Sigma
194 """Build a simple Gaussian Psf with linearly-varying ellipticity and size. 196 The Psf pattern increases sigma_x linearly along the x direction, and sigma_y 197 linearly along the y direction. 199 @param[in] detector: detector information (an lsst.afw.cameraGeom.Detector) 200 @return a psf (an instance of lsst.meas.algorithms.KernelPsf) 202 bbox = detector.getBBox()
203 dx = (self.config.psfMaxSigma - self.config.psfMinSigma) / bbox.getWidth()
204 dy = (self.config.psfMaxSigma - self.config.psfMinSigma) / bbox.getHeight()
205 sigmaXFunc = lsst.afw.math.PolynomialFunction2D(1)
206 sigmaXFunc.setParameter(0, self.config.psfMinSigma - dx * bbox.getMinX() - dy * bbox.getMinY())
207 sigmaXFunc.setParameter(1, dx)
208 sigmaXFunc.setParameter(2, 0.0)
209 sigmaYFunc = lsst.afw.math.PolynomialFunction2D(1)
210 sigmaYFunc.setParameter(0, self.config.psfMinSigma)
211 sigmaYFunc.setParameter(1, 0.0)
212 sigmaYFunc.setParameter(2, dy)
213 angleFunc = lsst.afw.math.PolynomialFunction2D(0)
215 spatialFuncList.append(sigmaXFunc)
216 spatialFuncList.append(sigmaYFunc)
217 spatialFuncList.append(angleFunc)
218 kernel = lsst.afw.math.AnalyticKernel(
219 self.config.psfImageSize, self.config.psfImageSize,
220 lsst.afw.math.GaussianFunction2D(self.config.psfMinSigma, self.config.psfMinSigma),
223 return lsst.meas.algorithms.KernelPsf(kernel)
226 """Build an ApCorrMap with random linearly-varying fields for all 227 flux fields registered for aperture correction. 229 These flux field names are used only as strings; there is no 230 connection to any actual algorithms with those names or the PSF model. 232 order = self.config.apCorrOrder
234 def makeRandomBoundedField():
235 """Make an upper-left triangular coefficient array appropriate 236 for a 2-d polynomial.""" 237 array = np.zeros((order + 1, order + 1), dtype=float)
238 for n
in range(order + 1):
239 array[n, 0:order + 1 - n] = self.
rng.randn(order + 1 - n)
240 return lsst.afw.math.ChebyshevBoundedField(bbox, array)
242 bbox = detector.getBBox()
243 apCorrMap = lsst.afw.image.ApCorrMap()
244 for name
in getApCorrNameSet():
245 apCorrMap.set(name +
"_flux", makeRandomBoundedField())
246 apCorrMap.set(name +
"_fluxSigma", makeRandomBoundedField())
def buildApCorrMap(self, detector)
def buildPsf(self, detector)
def run(self, butler, n, tractInfo, camera, catalog=None)
def makePointings(self, n, tractInfo)
def buildWcs(self, position, pa, detector)