Hide keyboard shortcuts

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 

25 

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 

33 

34 

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 ) 

62 

63 def setDefaults(self): 

64 self.skyMap.tractOverlap = 0.0 

65 

66 

67class 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: 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) 

105 

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 ) 

117 

118 

119class 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"): 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() 

163 

164 datasetName = self.config.coaddName + "Coadd_skyMap" 

165 

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) 

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: 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 ) 

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