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
mockCoadd.py
Go to the documentation of this file.
1 from builtins import range
2 #
3 # LSST Data Management System
4 # Copyright 2008-2015 AURA/LSST.
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 <https://www.lsstcorp.org/LegalNotices/>.
22 #
23 
24 import lsst.afw.image
25 import lsst.afw.geom
26 import lsst.pex.config
27 import lsst.afw.table
28 import lsst.pipe.base
29 from lsst.pipe.tasks.makeSkyMap import MakeSkyMapTask
30 from lsst.pipe.tasks.makeCoaddTempExp import MakeCoaddTempExpTask
31 from lsst.pipe.tasks.assembleCoadd import AssembleCoaddTask
32 from .mockObject import MockObjectTask
33 from .mockObservation import MockObservationTask
34 from .mockSelect import MockSelectImagesTask
35 
36 
37 class MockCoaddConfig(lsst.pex.config.Config):
38  makeSkyMap = lsst.pex.config.ConfigurableField(
39  doc="SkyMap builder subtask",
40  target=MakeSkyMapTask
41  )
42  mockObject = lsst.pex.config.ConfigurableField(
43  doc="Subtask that generates and draws the objects/sources in the mock images",
44  target=MockObjectTask
45  )
46  mockObservation = lsst.pex.config.ConfigurableField(
47  doc="Subtask that generates the Wcs, Psf, Calib, etc. of mock images",
48  target=MockObservationTask
49  )
50  coaddName = lsst.pex.config.Field(
51  doc="Coadd name used as a prefix for other datasets",
52  dtype=str,
53  optional=False,
54  default="deep"
55  )
56  nObservations = lsst.pex.config.Field(
57  doc="Number of mock observations to generate.",
58  dtype=int,
59  optional=False,
60  default=12
61  )
62  edgeBuffer = lsst.pex.config.Field(
63  doc=("Number of pixels by which to grow object bounding boxes when determining whether they land "
64  " completely on a generated image"),
65  dtype=int,
66  optional=False,
67  default=5
68  )
69 
70  def setupSkyMapPatches(self, nPatches=2, patchSize=400, pixelScale=0.2*lsst.afw.geom.arcseconds):
71  """
72  Set the nested [discrete] skymap config parameters such that the full tract
73  has nPatches x nPatches patches of the given size and pixel scale.
74  """
75  self.makeSkyMap.skyMap['discrete'].patchInnerDimensions = [patchSize, patchSize]
76  self.makeSkyMap.skyMap['discrete'].pixelScale = pixelScale.asArcseconds()
77  # multiply by 0.5 because we want a half-width; subtract 0.49 to ensure that we get the right
78  # number after skyMap.TractInfo rounds up.
79  radius = (0.5 * nPatches - 0.49) * patchSize * pixelScale.asDegrees()
80  self.makeSkyMap.skyMap['discrete'].radiusList = [radius]
81 
82  def setDefaults(self):
83  self.makeSkyMap.skyMap.name = 'discrete'
84  self.makeSkyMap.skyMap['discrete'].raList = [90.0]
85  self.makeSkyMap.skyMap['discrete'].decList = [0.0]
86  self.makeSkyMap.skyMap['discrete'].patchBorder = 10
87  self.makeSkyMap.skyMap['discrete'].projection = "TAN"
88  self.makeSkyMap.skyMap['discrete'].tractOverlap = 0.0
89  self.setupSkyMapPatches()
90 
91 
92 class MockCoaddTask(lsst.pipe.base.CmdLineTask):
93  """MockCoaddTask is a driver task for creating mock coadds. As opposed to more realistic
94  simulations, MockCoadd generates and uses extremely simple "toy" data that can be used to more
95  rigorously test the behavior of high-level task code because the expected results are
96  more easily predicted. In particular, calexps are generated directly from the truth catalog,
97  and contain only zero-noise stars that are created using the same Psf, Calib, and Wcs that will
98  be attached to the mock calexp.
99 
100  In addition to creating the mock calexps and truth catalogs, MockCoadd also contains driver
101  code to run the MakeSkyMap, MakeCoaddTempExp, and AssembleCoadd tasks on the mock calexps,
102  and code to directly create a mock coadd image using CoaddPsf, which can be compared to the
103  output of the regular coadd tasks to check that the coadd code and CoaddPsf are consistent.
104 
105  Note that aside from MakeSkyMapTask, the coadd tasks are *not* subtasks of MockCoaddTasks,
106  and their configs are not part of MockCoaddConfig; these are created locally within
107  MockCoaddTask methods when needed, as not all coadd task config options are appropriate
108  for the mock data generated by MockCoadd.
109  """
110 
111  ConfigClass = MockCoaddConfig
112 
113  _DefaultName = "MockCoadd"
114 
115  def __init__(self, **kwds):
116  """Construct a MockCoaddTask and the subtasks used for generating skymaps, objects,
117  and observations (i.e. calexp parameters).
118  """
119  lsst.pipe.base.CmdLineTask.__init__(self, **kwds)
120  self.makeSubtask("makeSkyMap")
121  self.makeSubtask("mockObject")
122  self.makeSubtask("mockObservation")
124  self.objectIdKey = self.schema.addField("objectId", type="L", doc="foreign key to truth catalog")
125  self.exposureIdKey = self.schema.addField("exposureId", type="L",
126  doc="foreign key to observation catalog")
127  self.centroidInBBoxKey = self.schema.addField(
128  "centroidInBBox", type="Flag",
129  doc="set if this source's center position is inside the generated image's bbox"
130  )
131  self.partialOverlapKey = self.schema.addField(
132  "partialOverlap", type="Flag",
133  doc="set if this source was not completely inside the generated image"
134  )
135 
136  def buildSkyMap(self, butler):
137  """Build the skymap for the mock dataset."""
138  return self.makeSkyMap.run(butler.dataRef(self.config.coaddName + "Coadd_skyMap")).skyMap
139 
140  def buildTruthCatalog(self, butler=None, skyMap=None, tract=0):
141  """Create and save (if butler is not None) a truth catalog containing all the mock objects.
142 
143  Must be run after buildSkyMap.
144 
145  Most of the work is delegated to the mockObject subtask.
146  """
147  if skyMap is None:
148  skyMap = butler.get(self.config.coaddName + "Coadd_skyMap")
149  catalog = self.mockObject.run(tractInfo=skyMap[tract])
150  if butler is not None:
151  butler.put(catalog, "truth", tract=tract)
152  return catalog
153 
154  def buildObservationCatalog(self, butler=None, skyMap=None, tract=0, camera=None):
155  """Create and save (if butler is not None) an ExposureCatalog of simulated observations,
156  containing the Psfs, Wcss, Calibs, etc. of the calexps to be simulated.
157 
158  Must be run after buildSkyMap.
159 
160  Most of the work is delegated to the mockObservation subtask.
161  """
162  if skyMap is None:
163  skyMap = butler.get(self.config.coaddName + "Coadd_skyMap")
164  if camera is None:
165  camera = butler.get("camera")
166  catalog = self.mockObservation.run(butler=butler,
167  n=self.config.nObservations, camera=camera,
168  tractInfo=skyMap[tract])
169  if butler is not None:
170  butler.put(catalog, "observations", tract=tract)
171  return catalog
172 
173  def buildInputImages(self, butler, obsCatalog=None, truthCatalog=None, tract=0):
174  """Use the truth catalog and observation catalog to create and save (if butler is not None)
175  mock calexps and an ExposureCatalog ('simsrc') that contains information about which objects
176  appear partially or fully in each exposure.
177 
178  Must be run after buildTruthCatalog and buildObservationCatalog.
179  """
180  if obsCatalog is None:
181  obsCatalog = butler.get("observations", tract=tract)
182  if truthCatalog is None:
183  truthCatalog = butler.get("truth", tract=tract)
184  ccdKey = obsCatalog.getSchema().find("ccd").key
185  visitKey = obsCatalog.getSchema().find("visit").key
186  simSrcCatalog = lsst.afw.table.SimpleCatalog(self.schema)
187  for obsRecord in obsCatalog:
188  ccd = obsRecord.getI(ccdKey)
189  visit = obsRecord.getI(visitKey)
190  self.log.info("Generating image for visit={visit}, ccd={ccd}".format(ccd=ccd, visit=visit))
191  exposure = lsst.afw.image.ExposureF(obsRecord.getBBox())
192  exposure.setCalib(obsRecord.getCalib())
193  exposure.setWcs(obsRecord.getWcs())
194  exposure.setPsf(obsRecord.getPsf())
195  exposure.getInfo().setApCorrMap(obsRecord.getApCorrMap())
196  for truthRecord in truthCatalog:
197  status = self.mockObject.drawSource(truthRecord, exposure, buffer=self.config.edgeBuffer)
198  if status:
199  simSrcRecord = simSrcCatalog.addNew()
200  simSrcRecord.setCoord(truthRecord.getCoord())
201  simSrcRecord.setL(self.objectIdKey, truthRecord.getId())
202  simSrcRecord.setL(self.exposureIdKey, obsRecord.getId())
203  simSrcRecord.setFlag(self.centroidInBBoxKey, obsRecord.contains(truthRecord.getCoord()))
204  simSrcRecord.setFlag(self.partialOverlapKey, status == 1)
205  self.log.info(" added object {id}".format(id=truthRecord.getId()))
206  exposure.getMaskedImage().getVariance().set(1.0)
207  if butler is not None:
208  butler.put(exposure, "calexp", ccd=ccd, visit=visit)
209  if butler is not None:
210  butler.put(simSrcCatalog, "simsrc", tract=tract)
211  return simSrcCatalog
212 
213  def buildAllInputs(self, butler):
214  """Convenience function that calls buildSkyMap, buildObservationCatalog, buildTruthCatalog,
215  and buildInputImages.
216  """
217  skyMap = self.buildSkyMap(butler)
218  observations = self.buildObservationCatalog(butler, skyMap=skyMap)
219  truth = self.buildTruthCatalog(butler, skyMap=skyMap)
220  simSrcCatalog = self.buildInputImages(butler, obsCatalog=observations, truthCatalog=truth)
221 
222  def makeCoaddTask(self, cls):
223  """Helper function to create a Coadd task with configuration appropriate for the simulations.
224 
225  MockCoaddTask does not include MakeCoaddTempExpTask or AssembleCoaddTask as subtasks, because
226  we want explicit control over their configs, rather than leaving this up to the user.
227  However, we have to install our own SelectImages task for both of these, so it made sense
228  to have a single method that would create one of these two tasks, set the config values we
229  want, and install the custom SelectImagesTask.
230  """
231  config = cls.ConfigClass()
232  config.coaddName = self.config.coaddName
233  config.select.retarget(MockSelectImagesTask)
234  if cls == MakeCoaddTempExpTask:
235  config.bgSubtracted = True
236  config.doPsfMatch = False
237  elif cls == AssembleCoaddTask:
238  config.doMatchBackgrounds = False
239  return cls(config)
240 
241  def iterPatchRefs(self, butler, tractInfo):
242  """Generator that iterates over the patches in a tract, yielding dataRefs.
243  """
244  nPatchX, nPatchY = tractInfo.getNumPatches()
245  for iPatchX in range(nPatchX):
246  for iPatchY in range(nPatchY):
247  patchRef = butler.dataRef(self.config.coaddName + "Coadd",
248  tract=tractInfo.getId(), patch="%d,%d" % (iPatchX, iPatchY),
249  filter='r')
250  yield patchRef
251 
252  def buildCoadd(self, butler, skyMap=None, tract=0):
253  """Run the coadd tasks (MakeCoaddTempExp and AssembleCoadd) on the mock data.
254 
255  Must be run after buildInputImages.
256  """
257  if skyMap is None:
258  skyMap = butler.get(self.config.coaddName + "Coadd_skyMap")
259  tractInfo = skyMap[tract]
260  makeCoaddTempExpTask = self.makeCoaddTask(MakeCoaddTempExpTask)
261  assembleCoaddTask = self.makeCoaddTask(AssembleCoaddTask)
262  for patchRef in self.iterPatchRefs(butler, tractInfo):
263  makeCoaddTempExpTask.run(patchRef)
264  for patchRef in self.iterPatchRefs(butler, tractInfo):
265  assembleCoaddTask.run(patchRef)
266 
267  def buildMockCoadd(self, butler, truthCatalog=None, skyMap=None, tract=0):
268  """Directly create a simulation of the coadd, using the CoaddPsf of the coadd exposure
269  and the truth catalog.
270 
271  Must be run after buildCoadd.
272  """
273  if truthCatalog is None:
274  truthCatalog = butler.get("truth", tract=tract)
275  if skyMap is None:
276  skyMap = butler.get(self.config.coaddName + "Coadd_skyMap")
277  tractInfo = skyMap[tract]
278  tractWcs = tractInfo.getWcs()
279  for patchRef in self.iterPatchRefs(butler, tractInfo):
280  exposure = patchRef.get(self.config.coaddName + "Coadd")
281  exposure.getMaskedImage().getImage().set(0.0)
283  exposure.getInfo().getCoaddInputs().ccds, exposure.getWcs()
284  )
285  exposure.setPsf(coaddPsf)
286  for truthRecord in truthCatalog:
287  self.mockObject.drawSource(truthRecord, exposure, buffer=0)
288  patchRef.put(exposure, self.config.coaddName + "Coadd_mock")
289 
290 
291 def run(root):
292  """Convenience function to create and run MockCoaddTask with default settings.
293  """
294  from .simpleMapper import makeDataRepo
295  butler = makeDataRepo(root=root)
296  task = MockCoaddTask()
297  task.buildAllInputs(butler)
298  task.buildCoadd(butler)
299  task.buildMockCoadd(butler)
static Schema makeMinimalSchema()
Return a minimal schema for Simple tables and records.
Definition: Simple.h:121
Custom catalog class for record/table subclasses that are guaranteed to have an ID, and should generally be sorted by that ID.
Definition: fwd.h:55
CoaddPsf is the Psf derived to be used for non-PSF-matched Coadd images.
Definition: CoaddPsf.h:45