lsst.pipe.tasks  13.0-66-gfbf2f2ce+5
makeDiscreteSkyMap.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 # Copyright 2008-2015 AURA/LSST.
4 #
5 # This product includes software developed by the
6 # LSST Project (http://www.lsst.org/).
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 from __future__ import absolute_import, division, print_function
23 import sys
24 import traceback
25 import lsst.geom
26 
27 import lsst.afw.image as afwImage
28 import lsst.afw.geom as afwGeom
29 import lsst.pex.config as pexConfig
30 import lsst.pipe.base as pipeBase
31 from lsst.skymap import DiscreteSkyMap, BaseSkyMap
32 from lsst.pipe.base import ArgumentParser
33 
34 
35 class MakeDiscreteSkyMapConfig(pexConfig.Config):
36  """Config for MakeDiscreteSkyMapTask
37  """
38  coaddName = pexConfig.Field(
39  doc="coadd name, e.g. deep, goodSeeing, chiSquared",
40  dtype=str,
41  default="deep",
42  )
43  skyMap = pexConfig.ConfigField(
44  dtype=BaseSkyMap.ConfigClass,
45  doc="SkyMap configuration parameters, excluding position and radius"
46  )
47  borderSize = pexConfig.Field(
48  doc="additional border added to the bounding box of the calexps, in degrees",
49  dtype=float,
50  default=0.0
51  )
52  doAppend = pexConfig.Field(
53  doc="append another tract to an existing DiscreteSkyMap on disk, if present?",
54  dtype=bool,
55  default=False
56  )
57  doWrite = pexConfig.Field(
58  doc="persist the skyMap?",
59  dtype=bool,
60  default=True,
61  )
62 
63  def setDefaults(self):
64  self.skyMap.tractOverlap = 0.0
65 
66 
67 class MakeDiscreteSkyMapRunner(pipeBase.TaskRunner):
68  """Run a task with all dataRefs at once, rather than one dataRef at a time.
69 
70  Call the run method of the task using two positional arguments:
71  - butler: data butler
72  - dataRefList: list of all dataRefs,
73  """
74  @staticmethod
75  def getTargetList(parsedCmd):
76  return [(parsedCmd.butler, parsedCmd.id.refList)]
77 
78  def __call__(self, args):
79  """
80  @param args Arguments for Task.run()
81 
82  @return:
83  - None if self.doReturnResults false
84  - A pipe_base Struct containing these fields if self.doReturnResults true:
85  - dataRef: the provided data reference
86  - metadata: task metadata after execution of run
87  - result: result returned by task run, or None if the task fails
88  """
89  butler, dataRefList = args
90  task = self.TaskClass(config=self.config, log=self.log)
91  result = None # in case the task fails
92  if self.doRaise:
93  result = task.run(butler, dataRefList)
94  else:
95  try:
96  result = task.run(butler, dataRefList)
97  except Exception as e:
98  task.log.fatal("Failed: %s" % e)
99  if not isinstance(e, pipeBase.TaskError):
100  traceback.print_exc(file=sys.stderr)
101  for dataRef in dataRefList:
102  task.writeMetadata(dataRef)
103 
104  if self.doReturnResults:
105  return pipeBase.Struct(
106  dataRefList=dataRefList,
107  metadata=task.metadata,
108  result=result,
109  )
110 
111 
112 class MakeDiscreteSkyMapTask(pipeBase.CmdLineTask):
113  """!Make a DiscreteSkyMap in a repository, using the bounding box of a set of calexps.
114 
115  The command-line and run signatures and config are sufficiently different from MakeSkyMapTask
116  that we don't inherit from it, but it is a replacement, so we use the same config/metadata names.
117  """
118  ConfigClass = MakeDiscreteSkyMapConfig
119  _DefaultName = "makeDiscreteSkyMap"
120  RunnerClass = MakeDiscreteSkyMapRunner
121 
122  def __init__(self, **kwargs):
123  pipeBase.CmdLineTask.__init__(self, **kwargs)
124 
125  @pipeBase.timeMethod
126  def run(self, butler, dataRefList):
127  """!Make a skymap from the bounds of the given set of calexps.
128 
129  @param[in] butler data butler used to save the SkyMap
130  @param[in] dataRefList dataRefs of calexps used to determine the size and pointing of the SkyMap
131  @return a pipeBase Struct containing:
132  - skyMap: the constructed SkyMap
133  """
134  self.log.info("Extracting bounding boxes of %d images" % len(dataRefList))
135  points = []
136  for dataRef in dataRefList:
137  if not dataRef.datasetExists("calexp"):
138  self.log.warn("CalExp for %s does not exist: ignoring" % (dataRef.dataId,))
139  continue
140  md = dataRef.get("calexp_md", immediate=True)
141  wcs = afwImage.makeWcs(md)
142  # nb: don't need to worry about xy0 because Exposure saves Wcs with CRPIX shifted by (-x0, -y0).
143  boxI = afwImage.bboxFromMetadata(md)
144  boxD = afwGeom.Box2D(boxI)
145  points.extend(tuple(wcs.pixelToSky(corner).getVector()) for corner in boxD.getCorners())
146  if len(points) == 0:
147  raise RuntimeError("No data found from which to compute convex hull")
148  self.log.info("Computing spherical convex hull")
149  polygon = lsst.geom.convexHull(points)
150  if polygon is None:
151  raise RuntimeError(
152  "Failed to compute convex hull of the vertices of all calexp bounding boxes; "
153  "they may not be hemispherical."
154  )
155  circle = polygon.getBoundingCircle()
156 
157  datasetName = self.config.coaddName + "Coadd_skyMap"
158 
159  skyMapConfig = DiscreteSkyMap.ConfigClass()
160  if self.config.doAppend and butler.datasetExists(datasetName):
161  oldSkyMap = butler.get(datasetName, immediate=True)
162  if not isinstance(oldSkyMap.config, DiscreteSkyMap.ConfigClass):
163  raise TypeError("Cannot append to existing non-discrete skymap")
164  compareLog = []
165  if not self.config.skyMap.compare(oldSkyMap.config, output=compareLog.append):
166  raise ValueError("Cannot append to existing skymap - configurations differ:", *compareLog)
167  skyMapConfig.raList.extend(oldSkyMap.config.raList)
168  skyMapConfig.decList.extend(oldSkyMap.config.decList)
169  skyMapConfig.radiusList.extend(oldSkyMap.config.radiusList)
170  skyMapConfig.update(**self.config.skyMap.toDict())
171  skyMapConfig.raList.append(circle.center[0])
172  skyMapConfig.decList.append(circle.center[1])
173  skyMapConfig.radiusList.append(circle.radius + self.config.borderSize)
174  skyMap = DiscreteSkyMap(skyMapConfig)
175 
176  for tractInfo in skyMap:
177  wcs = tractInfo.getWcs()
178  posBox = afwGeom.Box2D(tractInfo.getBBox())
179  pixelPosList = (
180  posBox.getMin(),
181  afwGeom.Point2D(posBox.getMaxX(), posBox.getMinY()),
182  posBox.getMax(),
183  afwGeom.Point2D(posBox.getMinX(), posBox.getMaxY()),
184  )
185  skyPosList = [wcs.pixelToSky(pos).getPosition(afwGeom.degrees) for pos in pixelPosList]
186  posStrList = ["(%0.3f, %0.3f)" % tuple(skyPos) for skyPos in skyPosList]
187  self.log.info("tract %s has corners %s (RA, Dec deg) and %s x %s patches" %
188  (tractInfo.getId(), ", ".join(posStrList),
189  tractInfo.getNumPatches()[0], tractInfo.getNumPatches()[1]))
190  if self.config.doWrite:
191  butler.put(skyMap, datasetName)
192  return pipeBase.Struct(
193  skyMap=skyMap
194  )
195 
196  def _getConfigName(self):
197  """Return None to disable saving config
198 
199  There's only one SkyMap per repository, so the config is redundant, and checking it means we can't
200  easily overwrite or append to an existing repository.
201  """
202  return None
203 
204  def _getMetadataName(self):
205  """Return None to disable saving metadata
206 
207  The metadata is not interesting, and by not saving it we can eliminate a dataset type.
208  """
209  return None
210 
211  @classmethod
212  def _makeArgumentParser(cls):
213  parser = ArgumentParser(name=cls._DefaultName)
214  parser.add_id_argument(name="--id", datasetType="calexp", help="data ID, e.g. --id visit=123 ccd=1,2")
215  return parser
def run(self, butler, dataRefList)
Make a skymap from the bounds of the given set of calexps.
Make a DiscreteSkyMap in a repository, using the bounding box of a set of calexps.