23 from __future__
import absolute_import, division, print_function
24 from builtins
import range
40 pixelScale = lsst.pex.config.Field(
41 dtype=float, default=0.2, optional=
False,
42 doc=
"Pixel scale for mock WCSs in arcseconds/pixel" 44 doRotate = lsst.pex.config.Field(
45 dtype=bool, default=
True, optional=
False,
46 doc=
"Whether to randomly rotate observations relative to the tract Wcs" 48 fluxMag0 = lsst.pex.config.Field(
49 dtype=float, default=1E11, optional=
False,
50 doc=
"Flux at zero magnitude used to define Calibs." 52 fluxMag0Sigma = lsst.pex.config.Field(
53 dtype=float, default=100.0, optional=
False,
54 doc=
"Error on flux at zero magnitude used to define Calibs; used to add scatter as well." 56 expTime = lsst.pex.config.Field(
57 dtype=float, default=60.0, optional=
False,
58 doc=
"Exposure time set in generated Calibs (does not affect flux or noise level)" 60 psfImageSize = lsst.pex.config.Field(
61 dtype=int, default=21, optional=
False,
62 doc=
"Image width and height of generated Psfs." 64 psfMinSigma = lsst.pex.config.Field(
65 dtype=float, default=1.5, optional=
False,
66 doc=
"Minimum radius for generated Psfs." 68 psfMaxSigma = lsst.pex.config.Field(
69 dtype=float, default=3.0, optional=
False,
70 doc=
"Maximum radius for generated Psfs." 72 apCorrOrder = lsst.pex.config.Field(
73 dtype=int, default=1, optional=
False,
74 doc=
"Polynomial order for aperture correction fields" 76 seed = lsst.pex.config.Field(dtype=int, default=1, doc=
"Seed for numpy random number generator")
80 """Task to generate mock Exposure parameters (Wcs, Psf, Calib), intended for use as a subtask 84 - document "pa" in detail; angle of what to what? 85 - document the catalog parameter of the run method 88 ConfigClass = MockObservationConfig
91 lsst.pipe.base.Task.__init__(self, **kwds)
93 self.
ccdKey = self.
schema.addField(
"ccd", type=np.int32, doc=
"CCD number")
94 self.
visitKey = self.
schema.addField(
"visit", type=np.int32, doc=
"visit number")
96 self.
filterKey = self.
schema.addField(
"filter", type=str, doc=
"Bandpass filter name", size=16)
97 self.
rng = np.random.RandomState(self.config.seed)
99 def run(self, butler, n, tractInfo, camera, catalog=None):
100 """Driver that generates an ExposureCatalog of mock observations. 102 @param[in] butler: a data butler 103 @param[in] n: number of pointings 104 @param[in] camera: camera geometry (an lsst.afw.cameraGeom.Camera) 105 @param[in] catalog: catalog to which to add observations (an ExposureCatalog); 106 if None then a new catalog is created. 108 @todo figure out what `pa` is and use that knowledge to set `boresightRotAng` and `rotType` 113 if not catalog.getSchema().contains(self.
schema):
114 raise ValueError(
"Catalog schema does not match Task schema")
119 exposureTime = self.config.expTime,
121 boresightRaDec = position,
123 for detector
in camera:
125 record = catalog.addNew()
126 record.setI(self.
ccdKey, detector.getId())
130 record.setWcs(self.buildWcs(position, pa, detector)) 131 record.setCalib(calib) 132 record.setVisitInfo(visitInfo) 133 record.setPsf(self.buildPsf(detector)) 136 record.setBBox(detector.getBBox()) 137 detectorId = detector.getId() 138 obj = butler.get("ccdExposureId", visit=visit, ccd=detectorId, immediate=
True)
144 """Generate (celestial) positions and rotation angles that define field locations. 146 Default implementation draws random pointings that are uniform in the tract's image 149 @param[in] n: number of pointings 150 @param[in] tractInfo: skymap tract (a lsst.skymap.TractInfo) 151 @return a Python iterable over (coord, angle) pairs: 152 - coord is an object position (an lsst.afw.coord.Coord) 153 - angle is a position angle (???) (an lsst.afw.geom.Angle) 155 The default implementation returns an iterator (i.e. the function is a "generator"), 156 but derived-class overrides may return any iterable. 158 wcs = tractInfo.getWcs()
162 x = self.
rng.rand() * bbox.getWidth() + bbox.getMinX()
163 y = self.
rng.rand() * bbox.getHeight() + bbox.getMinY()
164 pa = 0.0 * lsst.afw.geom.radians
165 if self.config.doRotate:
166 pa = self.
rng.rand() * 2.0 * np.pi * lsst.afw.geom.radians
167 yield wcs.pixelToSky(x, y), pa
170 """Build a simple TAN Wcs with no distortion and exactly-aligned CCDs. 172 @param[in] position: object position on sky (an lsst.afw.coord.Coord) 173 @param[in] pa: position angle (an lsst.afw.geom.Angle) 174 @param[in] detector: detector information (an lsst.afw.cameraGeom.Detector) 177 pixelScale = (self.config.pixelScale * lsst.afw.geom.arcseconds).asDegrees()
181 crpix = detector.transform(fpCtr, PIXELS).getPoint()
187 """Build a simple Calib object with exposure time fixed by config, fluxMag0 drawn from 188 a Gaussian defined by config, and mid-time set to DateTime.now(). 192 self.
rng.randn() * self.config.fluxMag0Sigma + self.config.fluxMag0,
193 self.config.fluxMag0Sigma
198 """Build a simple Gaussian Psf with linearly-varying ellipticity and size. 200 The Psf pattern increases sigma_x linearly along the x direction, and sigma_y 201 linearly along the y direction. 203 @param[in] detector: detector information (an lsst.afw.cameraGeom.Detector) 204 @return a psf (an instance of lsst.meas.algorithms.KernelPsf) 206 bbox = detector.getBBox()
207 dx = (self.config.psfMaxSigma - self.config.psfMinSigma) / bbox.getWidth()
208 dy = (self.config.psfMaxSigma - self.config.psfMinSigma) / bbox.getHeight()
209 sigmaXFunc = lsst.afw.math.PolynomialFunction2D(1)
210 sigmaXFunc.setParameter(0, self.config.psfMinSigma - dx * bbox.getMinX() - dy * bbox.getMinY())
211 sigmaXFunc.setParameter(1, dx)
212 sigmaXFunc.setParameter(2, 0.0)
213 sigmaYFunc = lsst.afw.math.PolynomialFunction2D(1)
214 sigmaYFunc.setParameter(0, self.config.psfMinSigma)
215 sigmaYFunc.setParameter(1, 0.0)
216 sigmaYFunc.setParameter(2, dy)
217 angleFunc = lsst.afw.math.PolynomialFunction2D(0)
219 spatialFuncList.append(sigmaXFunc)
220 spatialFuncList.append(sigmaYFunc)
221 spatialFuncList.append(angleFunc)
223 self.config.psfImageSize, self.config.psfImageSize,
224 lsst.afw.math.GaussianFunction2D(self.config.psfMinSigma, self.config.psfMinSigma),
230 """Build an ApCorrMap with random linearly-varying fields for all 231 flux fields registered for aperture correction. 233 These flux field names are used only as strings; there is no 234 connection to any actual algorithms with those names or the PSF model. 236 order = self.config.apCorrOrder
238 def makeRandomBoundedField():
239 """Make an upper-left triangular coefficient array appropriate 240 for a 2-d polynomial.""" 241 array = np.zeros((order + 1, order + 1), dtype=float)
242 for n
in range(order + 1):
243 array[n, 0:order + 1 - n] = self.
rng.randn(order + 1 - n)
246 bbox = detector.getBBox()
248 for name
in getApCorrNameSet():
249 apCorrMap.set(name +
"_flux", makeRandomBoundedField())
250 apCorrMap.set(name +
"_fluxSigma", makeRandomBoundedField())
254 """Build a random spacially-varying TransmissionCurve.""" 255 bbox = detector.getBBox()
256 return makeRandomTransmissionCurve(rng=self.
rng, maxRadius=max(bbox.getWidth(), bbox.getHeight()))
def buildTransmissionCurve(self, detector)
std::shared_ptr< Wcs > makeWcs(coord::Coord const &crval, geom::Point2D const &crpix, double CD11, double CD12, double CD21, double CD22)
static Schema makeMinimalSchema()
static CoordKey addFields(afw::table::Schema &schema, std::string const &name, std::string const &doc)
def buildApCorrMap(self, detector)
def buildPsf(self, detector)
static DateTime now(void)
def run(self, butler, n, tractInfo, camera, catalog=None)
def makePointings(self, n, tractInfo)
def buildWcs(self, position, pa, detector)