LSSTApplications  18.0.0+106,18.0.0+50,19.0.0,19.0.0+1,19.0.0+10,19.0.0+11,19.0.0+13,19.0.0+17,19.0.0+2,19.0.0-1-g20d9b18+6,19.0.0-1-g425ff20,19.0.0-1-g5549ca4,19.0.0-1-g580fafe+6,19.0.0-1-g6fe20d0+1,19.0.0-1-g7011481+9,19.0.0-1-g8c57eb9+6,19.0.0-1-gb5175dc+11,19.0.0-1-gdc0e4a7+9,19.0.0-1-ge272bc4+6,19.0.0-1-ge3aa853,19.0.0-10-g448f008b,19.0.0-12-g6990b2c,19.0.0-2-g0d9f9cd+11,19.0.0-2-g3d9e4fb2+11,19.0.0-2-g5037de4,19.0.0-2-gb96a1c4+3,19.0.0-2-gd955cfd+15,19.0.0-3-g2d13df8,19.0.0-3-g6f3c7dc,19.0.0-4-g725f80e+11,19.0.0-4-ga671dab3b+1,19.0.0-4-gad373c5+3,19.0.0-5-ga2acb9c+2,19.0.0-5-gfe96e6c+2,w.2020.01
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.sphgeom
25 
26 import lsst.geom as geom
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  exitStatus = 0 # exit status for shell
93  if self.doRaise:
94  result = task.runDataRef(butler, dataRefList)
95  else:
96  try:
97  result = task.runDataRef(butler, dataRefList)
98  except Exception as e:
99  task.log.fatal("Failed: %s" % e)
100  exitStatus = 1
101  if not isinstance(e, pipeBase.TaskError):
102  traceback.print_exc(file=sys.stderr)
103  for dataRef in dataRefList:
104  task.writeMetadata(dataRef)
105 
106  if self.doReturnResults:
107  return pipeBase.Struct(
108  dataRefList=dataRefList,
109  metadata=task.metadata,
110  result=result,
111  exitStatus=exitStatus,
112  )
113  else:
114  return pipeBase.Struct(
115  exitStatus=exitStatus,
116  )
117 
118 
119 class MakeDiscreteSkyMapTask(pipeBase.CmdLineTask):
120  """!Make a DiscreteSkyMap in a repository, using the bounding box of a set of calexps.
121 
122  The command-line and run signatures and config are sufficiently different from MakeSkyMapTask
123  that we don't inherit from it, but it is a replacement, so we use the same config/metadata names.
124  """
125  ConfigClass = MakeDiscreteSkyMapConfig
126  _DefaultName = "makeDiscreteSkyMap"
127  RunnerClass = MakeDiscreteSkyMapRunner
128 
129  def __init__(self, **kwargs):
130  pipeBase.CmdLineTask.__init__(self, **kwargs)
131 
132  @pipeBase.timeMethod
133  def runDataRef(self, butler, dataRefList):
134  """!Make a skymap from the bounds of the given set of calexps.
135 
136  @param[in] butler data butler used to save the SkyMap
137  @param[in] dataRefList dataRefs of calexps used to determine the size and pointing of the SkyMap
138  @return a pipeBase Struct containing:
139  - skyMap: the constructed SkyMap
140  """
141  self.log.info("Extracting bounding boxes of %d images" % len(dataRefList))
142  points = []
143  for dataRef in dataRefList:
144  if not dataRef.datasetExists("calexp"):
145  self.log.warn("CalExp for %s does not exist: ignoring" % (dataRef.dataId,))
146  continue
147  md = dataRef.get("calexp_md", immediate=True)
148  wcs = afwGeom.makeSkyWcs(md)
149  # nb: don't need to worry about xy0 because Exposure saves Wcs with CRPIX shifted by (-x0, -y0).
150  boxI = afwImage.bboxFromMetadata(md)
151  boxD = geom.Box2D(boxI)
152  points.extend(wcs.pixelToSky(corner).getVector() for corner in boxD.getCorners())
153  if len(points) == 0:
154  raise RuntimeError("No data found from which to compute convex hull")
155  self.log.info("Computing spherical convex hull")
156  polygon = lsst.sphgeom.ConvexPolygon.convexHull(points)
157  if polygon is None:
158  raise RuntimeError(
159  "Failed to compute convex hull of the vertices of all calexp bounding boxes; "
160  "they may not be hemispherical."
161  )
162  circle = polygon.getBoundingCircle()
163 
164  datasetName = self.config.coaddName + "Coadd_skyMap"
165 
166  skyMapConfig = DiscreteSkyMap.ConfigClass()
167  if self.config.doAppend and butler.datasetExists(datasetName):
168  oldSkyMap = butler.get(datasetName, immediate=True)
169  if not isinstance(oldSkyMap.config, DiscreteSkyMap.ConfigClass):
170  raise TypeError("Cannot append to existing non-discrete skymap")
171  compareLog = []
172  if not self.config.skyMap.compare(oldSkyMap.config, output=compareLog.append):
173  raise ValueError("Cannot append to existing skymap - configurations differ:", *compareLog)
174  skyMapConfig.raList.extend(oldSkyMap.config.raList)
175  skyMapConfig.decList.extend(oldSkyMap.config.decList)
176  skyMapConfig.radiusList.extend(oldSkyMap.config.radiusList)
177  skyMapConfig.update(**self.config.skyMap.toDict())
178  circleCenter = lsst.sphgeom.LonLat(circle.getCenter())
179  skyMapConfig.raList.append(circleCenter[0].asDegrees())
180  skyMapConfig.decList.append(circleCenter[1].asDegrees())
181  circleRadiusDeg = circle.getOpeningAngle().asDegrees()
182  skyMapConfig.radiusList.append(circleRadiusDeg + self.config.borderSize)
183  skyMap = DiscreteSkyMap(skyMapConfig)
184 
185  for tractInfo in skyMap:
186  wcs = tractInfo.getWcs()
187  posBox = geom.Box2D(tractInfo.getBBox())
188  pixelPosList = (
189  posBox.getMin(),
190  geom.Point2D(posBox.getMaxX(), posBox.getMinY()),
191  posBox.getMax(),
192  geom.Point2D(posBox.getMinX(), posBox.getMaxY()),
193  )
194  skyPosList = [wcs.pixelToSky(pos).getPosition(geom.degrees) for pos in pixelPosList]
195  posStrList = ["(%0.3f, %0.3f)" % tuple(skyPos) for skyPos in skyPosList]
196  self.log.info("tract %s has corners %s (RA, Dec deg) and %s x %s patches" %
197  (tractInfo.getId(), ", ".join(posStrList),
198  tractInfo.getNumPatches()[0], tractInfo.getNumPatches()[1]))
199  if self.config.doWrite:
200  butler.put(skyMap, datasetName)
201  return pipeBase.Struct(
202  skyMap=skyMap
203  )
204 
205  def _getConfigName(self):
206  """Return None to disable saving config
207 
208  There's only one SkyMap per repository, so the config is redundant, and checking it means we can't
209  easily overwrite or append to an existing repository.
210  """
211  return None
212 
213  def _getMetadataName(self):
214  """Return None to disable saving metadata
215 
216  The metadata is not interesting, and by not saving it we can eliminate a dataset type.
217  """
218  return None
219 
220  @classmethod
221  def _makeArgumentParser(cls):
222  parser = ArgumentParser(name=cls._DefaultName)
223  parser.add_id_argument(name="--id", datasetType="calexp", help="data ID, e.g. --id visit=123 ccd=1,2")
224  return parser
A floating-point coordinate rectangle geometry.
Definition: Box.h:413
def runDataRef(self, butler, dataRefList)
Make a skymap from the bounds of the given set of calexps.
static ConvexPolygon convexHull(std::vector< UnitVector3d > const &points)
convexHull returns the convex hull of the given set of points if it exists and throws an exception ot...
Definition: ConvexPolygon.h:65
LonLat represents a spherical coordinate (longitude/latitude angle) pair.
Definition: LonLat.h:48
std::shared_ptr< SkyWcs > makeSkyWcs(TransformPoint2ToPoint2 const &pixelsToFieldAngle, lsst::geom::Angle const &orientation, bool flipX, lsst::geom::SpherePoint const &boresight, std::string const &projection="TAN")
Construct a FITS SkyWcs from camera geometry.
Definition: SkyWcs.cc:516
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects...
lsst::geom::Box2I bboxFromMetadata(daf::base::PropertySet &metadata)
Determine the image bounding box from its metadata (FITS header)
Definition: Image.cc:694
Make a DiscreteSkyMap in a repository, using the bounding box of a set of calexps.