LSST Applications  21.0.0+04719a4bac,21.0.0-1-ga51b5d4+f5e6047307,21.0.0-11-g2b59f77+a9c1acf22d,21.0.0-11-ga42c5b2+86977b0b17,21.0.0-12-gf4ce030+76814010d2,21.0.0-13-g1721dae+760e7a6536,21.0.0-13-g3a573fe+768d78a30a,21.0.0-15-g5a7caf0+f21cbc5713,21.0.0-16-g0fb55c1+b60e2d390c,21.0.0-19-g4cded4ca+71a93a33c0,21.0.0-2-g103fe59+bb20972958,21.0.0-2-g45278ab+04719a4bac,21.0.0-2-g5242d73+3ad5d60fb1,21.0.0-2-g7f82c8f+8babb168e8,21.0.0-2-g8f08a60+06509c8b61,21.0.0-2-g8faa9b5+616205b9df,21.0.0-2-ga326454+8babb168e8,21.0.0-2-gde069b7+5e4aea9c2f,21.0.0-2-gecfae73+1d3a86e577,21.0.0-2-gfc62afb+3ad5d60fb1,21.0.0-25-g1d57be3cd+e73869a214,21.0.0-3-g357aad2+ed88757d29,21.0.0-3-g4a4ce7f+3ad5d60fb1,21.0.0-3-g4be5c26+3ad5d60fb1,21.0.0-3-g65f322c+e0b24896a3,21.0.0-3-g7d9da8d+616205b9df,21.0.0-3-ge02ed75+a9c1acf22d,21.0.0-4-g591bb35+a9c1acf22d,21.0.0-4-g65b4814+b60e2d390c,21.0.0-4-gccdca77+0de219a2bc,21.0.0-4-ge8a399c+6c55c39e83,21.0.0-5-gd00fb1e+05fce91b99,21.0.0-6-gc675373+3ad5d60fb1,21.0.0-64-g1122c245+4fb2b8f86e,21.0.0-7-g04766d7+cd19d05db2,21.0.0-7-gdf92d54+04719a4bac,21.0.0-8-g5674e7b+d1bd76f71f,master-gac4afde19b+a9c1acf22d,w.2021.13
LSST Data Management Base Package
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.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.skyMapskyMap.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  exitStatus = 0 # exit status for shell
92  if self.doRaise:
93  result = task.runDataRef(butler, dataRefList)
94  else:
95  try:
96  result = task.runDataRef(butler, dataRefList)
97  except Exception as e:
98  task.log.fatal("Failed: %s" % e)
99  exitStatus = 1
100  if not isinstance(e, pipeBase.TaskError):
101  traceback.print_exc(file=sys.stderr)
102  for dataRef in dataRefList:
103  task.writeMetadata(dataRef)
104 
105  if self.doReturnResults:
106  return pipeBase.Struct(
107  dataRefList=dataRefList,
108  metadata=task.metadata,
109  result=result,
110  exitStatus=exitStatus,
111  )
112  else:
113  return pipeBase.Struct(
114  exitStatus=exitStatus,
115  )
116 
117 
118 class MakeDiscreteSkyMapTask(pipeBase.CmdLineTask):
119  """!Make a DiscreteSkyMap in a repository, using the bounding box of a set of calexps.
120 
121  The command-line and run signatures and config are sufficiently different from MakeSkyMapTask
122  that we don't inherit from it, but it is a replacement, so we use the same config/metadata names.
123  """
124  ConfigClass = MakeDiscreteSkyMapConfig
125  _DefaultName = "makeDiscreteSkyMap"
126  RunnerClass = MakeDiscreteSkyMapRunner
127 
128  def __init__(self, **kwargs):
129  super().__init__(**kwargs)
130 
131  def runDataRef(self, butler, dataRefList):
132  """Make a skymap from the bounds of the given set of calexps using the butler.
133 
134  Parameters
135  ----------
136  butler : `lsst.daf.persistence.Butler`
137  Gen2 data butler used to save the SkyMap
138  dataRefList : iterable
139  A list of Gen2 data refs of calexps used to determin the size and pointing of the SkyMap
140  Returns
141  -------
142  struct : `lsst.pipe.base.Struct`
143  The returned struct has one attribute, ``skyMap``, which holds the returned SkyMap
144  """
145  wcs_md_tuple_list = []
146  oldSkyMap = None
147  datasetName = self.config.coaddName + "Coadd_skyMap"
148  for dataRef in dataRefList:
149  if not dataRef.datasetExists("calexp"):
150  self.log.warn("CalExp for %s does not exist: ignoring" % (dataRef.dataId,))
151  continue
152  wcs_md_tuple_list.append((dataRef.get("calexp_wcs", immediate=True),
153  dataRef.get("calexp_md", immediate=True)))
154  if self.config.doAppend and butler.datasetExists(datasetName):
155  oldSkyMap = butler.get(datasetName, immediate=True)
156  if not isinstance(oldSkyMap.config, DiscreteSkyMap.ConfigClass):
157  raise TypeError("Cannot append to existing non-discrete skymap")
158  compareLog = []
159  if not self.config.skyMap.compare(oldSkyMap.config, output=compareLog.append):
160  raise ValueError("Cannot append to existing skymap - configurations differ:", *compareLog)
161  result = self.runrun(wcs_md_tuple_list, oldSkyMap)
162  if self.config.doWrite:
163  butler.put(result.skyMap, datasetName)
164  return result
165 
166  @pipeBase.timeMethod
167  def run(self, wcs_md_tuple_list, oldSkyMap=None):
168  """Make a SkyMap from the bounds of the given set of calexp metadata.
169 
170  Parameters
171  ----------
172  wcs_md_tuple_list : iterable
173  A list of tuples with each element expected to be a (Wcs, PropertySet) pair
174  oldSkyMap : `lsst.skymap.DiscreteSkyMap`, option
175  The SkyMap to extend if appending
176  Returns
177  -------
178  struct : `lsst.pipe.base.Struct
179  The returned struct has one attribute, ``skyMap``, which holds the returned SkyMap
180  """
181  self.log.info("Extracting bounding boxes of %d images" % len(wcs_md_tuple_list))
182  points = []
183  for wcs, md in wcs_md_tuple_list:
184  # nb: don't need to worry about xy0 because Exposure saves Wcs with CRPIX shifted by (-x0, -y0).
185  boxI = afwImage.bboxFromMetadata(md)
186  boxD = geom.Box2D(boxI)
187  points.extend(wcs.pixelToSky(corner).getVector() for corner in boxD.getCorners())
188  if len(points) == 0:
189  raise RuntimeError("No data found from which to compute convex hull")
190  self.log.info("Computing spherical convex hull")
191  polygon = lsst.sphgeom.ConvexPolygon.convexHull(points)
192  if polygon is None:
193  raise RuntimeError(
194  "Failed to compute convex hull of the vertices of all calexp bounding boxes; "
195  "they may not be hemispherical."
196  )
197  circle = polygon.getBoundingCircle()
198 
199  skyMapConfig = DiscreteSkyMap.ConfigClass()
200  if oldSkyMap:
201  skyMapConfig.raList.extend(oldSkyMap.config.raList)
202  skyMapConfig.decList.extend(oldSkyMap.config.decList)
203  skyMapConfig.radiusList.extend(oldSkyMap.config.radiusList)
204  skyMapConfig.update(**self.config.skyMap.toDict())
205  circleCenter = lsst.sphgeom.LonLat(circle.getCenter())
206  skyMapConfig.raList.append(circleCenter[0].asDegrees())
207  skyMapConfig.decList.append(circleCenter[1].asDegrees())
208  circleRadiusDeg = circle.getOpeningAngle().asDegrees()
209  skyMapConfig.radiusList.append(circleRadiusDeg + self.config.borderSize)
210  skyMap = DiscreteSkyMap(skyMapConfig)
211 
212  for tractInfo in skyMap:
213  wcs = tractInfo.getWcs()
214  posBox = geom.Box2D(tractInfo.getBBox())
215  pixelPosList = (
216  posBox.getMin(),
217  geom.Point2D(posBox.getMaxX(), posBox.getMinY()),
218  posBox.getMax(),
219  geom.Point2D(posBox.getMinX(), posBox.getMaxY()),
220  )
221  skyPosList = [wcs.pixelToSky(pos).getPosition(geom.degrees) for pos in pixelPosList]
222  posStrList = ["(%0.3f, %0.3f)" % tuple(skyPos) for skyPos in skyPosList]
223  self.log.info("tract %s has corners %s (RA, Dec deg) and %s x %s patches" %
224  (tractInfo.getId(), ", ".join(posStrList),
225  tractInfo.getNumPatches()[0], tractInfo.getNumPatches()[1]))
226  return pipeBase.Struct(
227  skyMap=skyMap
228  )
229 
230  def _getConfigName(self):
231  """Return None to disable saving config
232 
233  There's only one SkyMap per repository, so the config is redundant, and checking it means we can't
234  easily overwrite or append to an existing repository.
235  """
236  return None
237 
238  def _getMetadataName(self):
239  """Return None to disable saving metadata
240 
241  The metadata is not interesting, and by not saving it we can eliminate a dataset type.
242  """
243  return None
244 
245  @classmethod
246  def _makeArgumentParser(cls):
247  parser = ArgumentParser(name=cls._DefaultName_DefaultName)
248  parser.add_id_argument(name="--id", datasetType="calexp", help="data ID, e.g. --id visit=123 ccd=1,2")
249  return parser
A floating-point coordinate rectangle geometry.
Definition: Box.h:413
Make a DiscreteSkyMap in a repository, using the bounding box of a set of calexps.
def run(self, wcs_md_tuple_list, oldSkyMap=None)
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
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