LSSTApplications  11.0-13-gbb96280,12.1+18,12.1+7,12.1-1-g14f38d3+72,12.1-1-g16c0db7+5,12.1-1-g5961e7a+84,12.1-1-ge22e12b+23,12.1-11-g06625e2+4,12.1-11-g0d7f63b+4,12.1-19-gd507bfc,12.1-2-g7dda0ab+38,12.1-2-gc0bc6ab+81,12.1-21-g6ffe579+2,12.1-21-gbdb6c2a+4,12.1-24-g941c398+5,12.1-3-g57f6835+7,12.1-3-gf0736f3,12.1-37-g3ddd237,12.1-4-gf46015e+5,12.1-5-g06c326c+20,12.1-5-g648ee80+3,12.1-5-gc2189d7+4,12.1-6-ga608fc0+1,12.1-7-g3349e2a+5,12.1-7-gfd75620+9,12.1-9-g577b946+5,12.1-9-gc4df26a+10
LSSTDataManagementBasePackage
mockObservation.py
Go to the documentation of this file.
1 from builtins import range
2 #
3 # LSST Data Management System
4 # Copyright 2008, 2009, 2010, 2011, 2012 LSST Corporation.
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 <http://www.lsstcorp.org/LegalNotices/>.
22 #
23 
24 import numpy as np
25 
26 import lsst.pex.config
27 import lsst.afw.table
28 import lsst.afw.geom
29 from lsst.afw.cameraGeom import PIXELS, FOCAL_PLANE
30 import lsst.afw.image
31 import lsst.afw.math
32 import lsst.afw.detection
33 import lsst.pipe.base
34 from lsst.meas.base.apCorrRegistry import getApCorrNameSet
35 
36 
37 class MockObservationConfig(lsst.pex.config.Config):
38  pixelScale = lsst.pex.config.Field(
39  dtype=float, default=0.2, optional=False,
40  doc="Pixel scale for mock WCSs in arcseconds/pixel"
41  )
42  doRotate = lsst.pex.config.Field(
43  dtype=bool, default=True, optional=False,
44  doc="Whether to randomly rotate observations relative to the tract Wcs"
45  )
46  fluxMag0 = lsst.pex.config.Field(
47  dtype=float, default=1E11, optional=False,
48  doc="Flux at zero magnitude used to define Calibs."
49  )
50  fluxMag0Sigma = lsst.pex.config.Field(
51  dtype=float, default=100.0, optional=False,
52  doc="Error on flux at zero magnitude used to define Calibs; used to add scatter as well."
53  )
54  expTime = lsst.pex.config.Field(
55  dtype=float, default=60.0, optional=False,
56  doc="Exposure time set in generated Calibs (does not affect flux or noise level)"
57  )
58  psfImageSize = lsst.pex.config.Field(
59  dtype=int, default=21, optional=False,
60  doc="Image width and height of generated Psfs."
61  )
62  psfMinSigma = lsst.pex.config.Field(
63  dtype=float, default=1.5, optional=False,
64  doc="Minimum radius for generated Psfs."
65  )
66  psfMaxSigma = lsst.pex.config.Field(
67  dtype=float, default=3.0, optional=False,
68  doc="Maximum radius for generated Psfs."
69  )
70  apCorrOrder = lsst.pex.config.Field(
71  dtype=int, default=1, optional=False,
72  doc="Polynomial order for aperture correction fields"
73  )
74  seed = lsst.pex.config.Field(dtype=int, default=1, doc="Seed for numpy random number generator")
75 
76 
77 class MockObservationTask(lsst.pipe.base.Task):
78  """Task to generate mock Exposure parameters (Wcs, Psf, Calib), intended for use as a subtask
79  of MockCoaddTask.
80 
81  @todo:
82  - document "pa" in detail; angle of what to what?
83  - document the catalog parameter of the run method
84  """
85 
86  ConfigClass = MockObservationConfig
87 
88  def __init__(self, **kwds):
89  lsst.pipe.base.Task.__init__(self, **kwds)
91  self.ccdKey = self.schema.addField("ccd", type=int, doc="CCD number")
92  self.visitKey = self.schema.addField("visit", type=int, doc="visit number")
93  self.pointingKey = lsst.afw.table.CoordKey.addFields(self.schema, "pointing", "center of visit")
94  self.rng = np.random.RandomState(self.config.seed)
95 
96  def run(self, butler, n, tractInfo, camera, catalog=None):
97  """Driver that generates an ExposureCatalog of mock observations.
98 
99  @param[in] butler: a data butler
100  @param[in] n: number of pointings
101  @param[in] camera: camera geometry (an lsst.afw.cameraGeom.Camera)
102  @param[in] catalog: catalog to which to add observations (an ExposureCatalog);
103  if None then a new catalog is created.
104 
105  @todo figure out what `pa` is and use that knowledge to set `boresightRotAng` and `rotType`
106  """
107  if catalog is None:
108  catalog = lsst.afw.table.ExposureCatalog(self.schema)
109  else:
110  if not catalog.getSchema().contains(self.schema):
111  raise ValueError("Catalog schema does not match Task schema")
112  visit = 1
113 
114  for position, pa in self.makePointings(n, tractInfo):
115  visitInfo = lsst.afw.image.makeVisitInfo(
116  exposureTime = self.config.expTime,
118  boresightRaDec = position,
119  )
120  for detector in camera:
121  calib = self.buildCalib()
122  record = catalog.addNew()
123  record.setI(self.ccdKey, detector.getId())
124  record.setI(self.visitKey, visit)
125  record.set(self.pointingKey, position)
126  record.setWcs(self.buildWcs(position, pa, detector))
127  record.setCalib(calib)
128  record.setVisitInfo(visitInfo)
129  record.setPsf(self.buildPsf(detector))
130  record.setApCorrMap(self.buildApCorrMap(detector))
131  record.setBBox(detector.getBBox())
132  detectorId = detector.getId()
133  obj = butler.get("ccdExposureId", visit=visit, ccd=detectorId, immediate=True)
134  record.setId(obj)
135  visit += 1
136  return catalog
137 
138  def makePointings(self, n, tractInfo):
139  """Generate (celestial) positions and rotation angles that define field locations.
140 
141  Default implementation draws random pointings that are uniform in the tract's image
142  coordinate system.
143 
144  @param[in] n: number of pointings
145  @param[in] tractInfo: skymap tract (a lsst.skymap.TractInfo)
146  @return a Python iterable over (coord, angle) pairs:
147  - coord is an object position (an lsst.afw.coord.Coord)
148  - angle is a position angle (???) (an lsst.afw.geom.Angle)
149 
150  The default implementation returns an iterator (i.e. the function is a "generator"),
151  but derived-class overrides may return any iterable.
152  """
153  wcs = tractInfo.getWcs()
154  bbox = lsst.afw.geom.Box2D(tractInfo.getBBox())
155  bbox.grow(lsst.afw.geom.Extent2D(-0.1 * bbox.getWidth(), -0.1 * bbox.getHeight()))
156  for i in range(n):
157  x = self.rng.rand() * bbox.getWidth() + bbox.getMinX()
158  y = self.rng.rand() * bbox.getHeight() + bbox.getMinY()
159  pa = 0.0 * lsst.afw.geom.radians
160  if self.config.doRotate:
161  pa = self.rng.rand() * 2.0 * np.pi * lsst.afw.geom.radians
162  yield wcs.pixelToSky(x, y), pa
163 
164  def buildWcs(self, position, pa, detector):
165  """Build a simple TAN Wcs with no distortion and exactly-aligned CCDs.
166 
167  @param[in] position: object position on sky (an lsst.afw.coord.Coord)
168  @param[in] pa: position angle (an lsst.afw.geom.Angle)
169  @param[in] detector: detector information (an lsst.afw.cameraGeom.Detector)
170  """
171  crval = position
172  pixelScale = (self.config.pixelScale * lsst.afw.geom.arcseconds).asDegrees()
175  fpCtr = detector.makeCameraPoint(lsst.afw.geom.Point2D(0, 0), FOCAL_PLANE)
176  crpix = detector.transform(fpCtr, PIXELS).getPoint()
177 
178  wcs = lsst.afw.image.makeWcs(crval, crpix, *cd.getMatrix().flatten())
179  return wcs
180 
181  def buildCalib(self):
182  """Build a simple Calib object with exposure time fixed by config, fluxMag0 drawn from
183  a Gaussian defined by config, and mid-time set to DateTime.now().
184  """
185  calib = lsst.afw.image.Calib()
186  calib.setFluxMag0(
187  self.rng.randn() * self.config.fluxMag0Sigma + self.config.fluxMag0,
188  self.config.fluxMag0Sigma
189  )
190  return calib
191 
192  def buildPsf(self, detector):
193  """Build a simple Gaussian Psf with linearly-varying ellipticity and size.
194 
195  The Psf pattern increases sigma_x linearly along the x direction, and sigma_y
196  linearly along the y direction.
197 
198  @param[in] detector: detector information (an lsst.afw.cameraGeom.Detector)
199  @return a psf (an instance of lsst.meas.algorithms.KernelPsf)
200  """
201  bbox = detector.getBBox()
202  dx = (self.config.psfMaxSigma - self.config.psfMinSigma) / bbox.getWidth()
203  dy = (self.config.psfMaxSigma - self.config.psfMinSigma) / bbox.getHeight()
204  sigmaXFunc = lsst.afw.math.PolynomialFunction2D(1)
205  sigmaXFunc.setParameter(0, self.config.psfMinSigma - dx * bbox.getMinX() - dy * bbox.getMinY())
206  sigmaXFunc.setParameter(1, dx)
207  sigmaXFunc.setParameter(2, 0.0)
208  sigmaYFunc = lsst.afw.math.PolynomialFunction2D(1)
209  sigmaYFunc.setParameter(0, self.config.psfMinSigma)
210  sigmaYFunc.setParameter(1, 0.0)
211  sigmaYFunc.setParameter(2, dy)
212  angleFunc = lsst.afw.math.PolynomialFunction2D(0)
213  spatialFuncList = lsst.afw.math.Function2DList()
214  spatialFuncList.append(sigmaXFunc)
215  spatialFuncList.append(sigmaYFunc)
216  spatialFuncList.append(angleFunc)
218  self.config.psfImageSize, self.config.psfImageSize,
219  lsst.afw.math.GaussianFunction2D(self.config.psfMinSigma, self.config.psfMinSigma),
220  spatialFuncList
221  )
222  return lsst.meas.algorithms.KernelPsf(kernel)
223 
224  def buildApCorrMap(self, detector):
225  """Build an ApCorrMap with random linearly-varying fields for all
226  flux fields registered for aperture correction.
227 
228  These flux field names are used only as strings; there is no
229  connection to any actual algorithms with those names or the PSF model.
230  """
231  order = self.config.apCorrOrder
232 
233  def makeRandomBoundedField():
234  """Make an upper-left triangular coefficient array appropriate
235  for a 2-d polynomial."""
236  array = np.zeros((order + 1, order + 1), dtype=float)
237  for n in range(order + 1):
238  array[n, 0:order + 1 - n] = self.rng.randn(order + 1 - n)
239  return lsst.afw.math.ChebyshevBoundedField(bbox, array)
240 
241  bbox = detector.getBBox()
242  apCorrMap = lsst.afw.image.ApCorrMap()
243  for name in getApCorrNameSet():
244  apCorrMap.set(name + "_flux", makeRandomBoundedField())
245  apCorrMap.set(name + "_fluxSigma", makeRandomBoundedField())
246  return apCorrMap
A Psf defined by a Kernel.
Definition: KernelPsf.h:33
A thin wrapper around std::map to allow aperture corrections to be attached to Exposures.
Definition: ApCorrMap.h:42
static CoordKey addFields(afw::table::Schema &schema, std::string const &name, std::string const &doc)
Add a pair of _ra, _dec fields to a Schema, and return a CoordKey that points to them.
Describe an exposure&#39;s calibration.
Definition: Calib.h:82
static LinearTransform makeScaling(double s)
static LinearTransform makeRotation(Angle t)
boost::shared_ptr< Wcs > makeWcs(coord::Coord const &crval, geom::Point2D const &crpix, double CD11, double CD12, double CD21, double CD22)
Create a Wcs object from crval, crpix, CD, using CD elements (useful from python) ...
Definition: makeWcs.cc:138
def getApCorrNameSet
Return a copy of the set of field name prefixes for fluxes that should be aperture corrected...
A BoundedField based on 2-d Chebyshev polynomials of the first kind.
Custom catalog class for ExposureRecord/Table.
Definition: Exposure.h:56
static Schema makeMinimalSchema()
Return a minimal schema for Exposure tables and records.
Definition: Exposure.h:169
A floating-point coordinate rectangle geometry.
Definition: Box.h:271
A kernel described by a function.
Definition: Kernel.h:625
static DateTime now(void)
Return current time as a DateTime.
Definition: DateTime.cc:533