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