Coverage for python/lsst/pipe/tasks/makeDiscreteSkyMap.py : 22%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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#
22import sys
23import traceback
24import lsst.sphgeom
26import lsst.geom as geom
27import lsst.afw.image as afwImage
28import lsst.pex.config as pexConfig
29import lsst.pipe.base as pipeBase
30from lsst.skymap import DiscreteSkyMap, BaseSkyMap
31from lsst.pipe.base import ArgumentParser
34class 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 )
62 def setDefaults(self):
63 self.skyMap.tractOverlap = 0.0
66class MakeDiscreteSkyMapRunner(pipeBase.TaskRunner):
67 """Run a task with all dataRefs at once, rather than one dataRef at a time.
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)]
77 def __call__(self, args):
78 """
79 @param args Arguments for Task.run()
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)
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 )
118class MakeDiscreteSkyMapTask(pipeBase.CmdLineTask):
119 """!Make a DiscreteSkyMap in a repository, using the bounding box of a set of calexps.
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
128 def __init__(self, **kwargs):
129 super().__init__(**kwargs)
131 def runDataRef(self, butler, dataRefList):
132 """Make a skymap from the bounds of the given set of calexps using the butler.
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.warning("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.run(wcs_md_tuple_list, oldSkyMap)
162 if self.config.doWrite:
163 butler.put(result.skyMap, datasetName)
164 return result
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.
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()
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)
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 )
230 def _getConfigName(self):
231 """Return None to disable saving config
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
238 def _getMetadataName(self):
239 """Return None to disable saving metadata
241 The metadata is not interesting, and by not saving it we can eliminate a dataset type.
242 """
243 return None
245 @classmethod
246 def _makeArgumentParser(cls):
247 parser = ArgumentParser(name=cls._DefaultName)
248 parser.add_id_argument(name="--id", datasetType="calexp", help="data ID, e.g. --id visit=123 ccd=1,2")
249 return parser