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

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.afw.geom as afwGeom
29import lsst.pex.config as pexConfig
30import lsst.pipe.base as pipeBase
31from lsst.skymap import DiscreteSkyMap, BaseSkyMap
32from lsst.pipe.base import ArgumentParser
35class 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 )
63 def setDefaults(self):
64 self.skyMap.tractOverlap = 0.0
67class MakeDiscreteSkyMapRunner(pipeBase.TaskRunner):
68 """Run a task with all dataRefs at once, rather than one dataRef at a time.
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)]
78 def __call__(self, args):
79 """
80 @param args Arguments for Task.run()
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: 93 ↛ 94line 93 didn't jump to line 94, because the condition on line 93 was never true
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)
106 if self.doReturnResults: 106 ↛ 114line 106 didn't jump to line 114, because the condition on line 106 was never false
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 )
119class MakeDiscreteSkyMapTask(pipeBase.CmdLineTask):
120 """!Make a DiscreteSkyMap in a repository, using the bounding box of a set of calexps.
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
129 def __init__(self, **kwargs):
130 pipeBase.CmdLineTask.__init__(self, **kwargs)
132 @pipeBase.timeMethod
133 def runDataRef(self, butler, dataRefList):
134 """!Make a skymap from the bounds of the given set of calexps.
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"): 144 ↛ 145line 144 didn't jump to line 145, because the condition on line 144 was never true
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: 153 ↛ 154line 153 didn't jump to line 154, because the condition on line 153 was never true
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: 157 ↛ 158line 157 didn't jump to line 158, because the condition on line 157 was never true
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()
164 datasetName = self.config.coaddName + "Coadd_skyMap"
166 skyMapConfig = DiscreteSkyMap.ConfigClass()
167 if self.config.doAppend and butler.datasetExists(datasetName): 167 ↛ 168line 167 didn't jump to line 168, because the condition on line 167 was never true
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)
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: 199 ↛ 200line 199 didn't jump to line 200, because the condition on line 199 was never true
200 butler.put(skyMap, datasetName)
201 return pipeBase.Struct(
202 skyMap=skyMap
203 )
205 def _getConfigName(self):
206 """Return None to disable saving config
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
213 def _getMetadataName(self):
214 """Return None to disable saving metadata
216 The metadata is not interesting, and by not saving it we can eliminate a dataset type.
217 """
218 return None
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