Coverage for python/lsst/pipe/tasks/coaddInputRecorder.py: 23%

96 statements  

« prev     ^ index     » next       coverage.py v6.4.2, created at 2022-08-06 02:04 -0700

1# 

2# LSST Data Management System 

3# Copyright 2008, 2009, 2010, 2011, 2012 LSST Corporation. 

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 <http://www.lsstcorp.org/LegalNotices/>. 

21# 

22import numpy 

23 

24import lsst.pex.config as pexConfig 

25import lsst.afw.table as afwTable 

26import lsst.afw.image as afwImage 

27import lsst.pipe.base as pipeBase 

28from lsst.meas.algorithms import CoaddPsf, makeCoaddApCorrMap 

29 

30__all__ = ["CoaddInputRecorderTask"] 

31 

32 

33class CoaddInputRecorderConfig(pexConfig.Config): 

34 """Config for CoaddInputRecorderTask 

35 

36 The inputRecorder section of the various coadd tasks' configs should generally agree, 

37 or the schemas created by earlier tasks (like MakeCoaddTempExpTask) will not contain 

38 the fields filled by later tasks (like AssembleCoaddTask). 

39 """ 

40 

41 saveEmptyCcds = pexConfig.Field( 

42 dtype=bool, default=False, optional=False, 

43 doc=("Add records for CCDs we iterated over but did not add a coaddTempExp" 

44 " due to a lack of unmasked pixels in the coadd footprint.") 

45 ) 

46 saveErrorCcds = pexConfig.Field( 

47 dtype=bool, default=False, optional=False, 

48 doc=("Add records for CCDs we iterated over but did not add a coaddTempExp" 

49 " due to an exception (often due to the calexp not being found on disk).") 

50 ) 

51 saveVisitGoodPix = pexConfig.Field( 

52 dtype=bool, default=True, optional=False, 

53 doc=("Save the total number of good pixels in each coaddTempExp (redundant with a sum of" 

54 " good pixels in associated CCDs)") 

55 ) 

56 saveCcdWeights = pexConfig.Field( 

57 dtype=bool, default=True, optional=False, 

58 doc=("Save weights in the CCDs table as well as the visits table?" 

59 " (This is necessary for easy construction of CoaddPsf, but otherwise duplicate information.)") 

60 ) 

61 

62 

63class CoaddTempExpInputRecorder: 

64 """A helper class for CoaddInputRecorderTask, managing the CoaddInputs object for that single 

65 CoaddTempExp. This will contain a single 'visit' record for the CoaddTempExp and a number of 'ccd' 

66 records. 

67 

68 Should generally be created by calling CoaddInputRecorderTask.makeCoaddTempExp(). 

69 """ 

70 

71 def __init__(self, task, visitId, num=0): 

72 """Constructor 

73 

74 @param task The CoaddInputRecorderTask that is utilising us 

75 @param visitId Identifier (integer) for the visit 

76 @param num Number of CCDs for this visit that overlap this 

77 patch (for reserving memory) 

78 """ 

79 self.task = task 

80 self.coaddInputs = self.task.makeCoaddInputs() 

81 self.coaddInputs.visits.reserve(1) 

82 if num > 0: 

83 self.coaddInputs.ccds.reserve(num) 

84 self.visitRecord = self.coaddInputs.visits.addNew() 

85 self.visitRecord.setId(visitId) 

86 

87 def addCalExp(self, calExp, ccdId, nGoodPix): 

88 """Add a 'ccd' record for a calexp just added to the CoaddTempExp 

89 

90 @param[in] calExp Calibrated exposure just added to the CoaddTempExp, or None in case of 

91 failures that should nonetheless be tracked. Should be the original 

92 calexp, in that it should contain the original Psf and Wcs, not the 

93 warped and/or matched ones. 

94 @param[in] ccdId A unique numeric ID for the Exposure. 

95 @param[in] nGoodPix Number of good pixels this image will contribute to the CoaddTempExp. 

96 If saveEmptyCcds is not set and this value is zero, no record will be 

97 added. 

98 """ 

99 if nGoodPix == 0 and not self.task.config.saveEmptyCcds: 

100 return 

101 record = self.coaddInputs.ccds.addNew() 

102 record.setId(ccdId) 

103 record.setL(self.task.ccdVisitKey, self.visitRecord.getId()) 

104 try: 

105 record.setI(self.task.ccdCcdKey, calExp.getDetector().getId()) 

106 except Exception as e: 

107 self.task.log.warning("Error getting detector serial number in visit %d; using -1; error=%s", 

108 self.visitRecord.getId(), e) 

109 record.setI(self.task.ccdCcdKey, -1) 

110 record.setI(self.task.ccdGoodPixKey, nGoodPix) 

111 if calExp is not None: 

112 self._setExposureInfoInRecord(exposure=calExp, record=record) 

113 if self.task.config.saveCcdWeights: 

114 record.setD(self.task.ccdWeightKey, 1.0) # No weighting or overlap when warping 

115 record.set(self.task.ccdFilterKey, calExp.getFilter().physicalLabel) 

116 

117 def finish(self, coaddTempExp, nGoodPix=None): 

118 """Finish creating the CoaddInputs for a CoaddTempExp. 

119 

120 @param[in,out] coaddTempExp Exposure object from which to obtain the PSF, WCS, and bounding 

121 box for the entry in the 'visits' table. On return, the completed 

122 CoaddInputs object will be attached to it. 

123 @param[in] nGoodPix Total number of good pixels in the CoaddTempExp; ignored unless 

124 saveVisitGoodPix is true. 

125 """ 

126 self._setExposureInfoInRecord(exposure=coaddTempExp, record=self.visitRecord) 

127 if self.task.config.saveVisitGoodPix: 

128 self.visitRecord.setI(self.task.visitGoodPixKey, nGoodPix) 

129 coaddTempExp.getInfo().setCoaddInputs(self.coaddInputs) 

130 wcs = coaddTempExp.getWcs() 

131 if False: 

132 # This causes a test failure, pending fix in issue HSC-802 

133 coaddTempExp.setPsf(CoaddPsf(self.coaddInputs.ccds, wcs)) 

134 apCorrMap = makeCoaddApCorrMap(self.coaddInputs.ccds, coaddTempExp.getBBox(afwImage.PARENT), wcs) 

135 coaddTempExp.getInfo().setApCorrMap(apCorrMap) 

136 

137 def _setExposureInfoInRecord(self, exposure, record): 

138 """Set exposure info and bbox in an ExposureTable record 

139 

140 @param[in] exposure exposure whose info is to be recorded 

141 @param[in,out] record record of an ExposureTable to set 

142 """ 

143 info = exposure.getInfo() 

144 record.setPsf(info.getPsf()) 

145 record.setWcs(info.getWcs()) 

146 record.setPhotoCalib(info.getPhotoCalib()) 

147 record.setApCorrMap(info.getApCorrMap()) 

148 record.setValidPolygon(info.getValidPolygon()) 

149 record.setVisitInfo(info.getVisitInfo()) 

150 record.setBBox(exposure.getBBox()) 

151 record.setTransmissionCurve(info.getTransmissionCurve()) 

152 

153 

154class CoaddInputRecorderTask(pipeBase.Task): 

155 """Subtask that handles filling a CoaddInputs object for a coadd exposure, tracking the CCDs and 

156 visits that went into a coadd. 

157 

158 The interface here is a little messy, but I think this is at least partly a product of a bit of 

159 messiness in the coadd code it's plugged into. I hope #2590 might result in a better design. 

160 """ 

161 

162 ConfigClass = CoaddInputRecorderConfig 

163 

164 def __init__(self, *args, **kwargs): 

165 pipeBase.Task.__init__(self, *args, **kwargs) 

166 self.visitSchema = afwTable.ExposureTable.makeMinimalSchema() 

167 if self.config.saveVisitGoodPix: 

168 self.visitGoodPixKey = self.visitSchema.addField("goodpix", type=numpy.int32, 

169 doc="Number of good pixels in the coaddTempExp") 

170 self.visitWeightKey = self.visitSchema.addField("weight", type=float, 

171 doc="Weight for this visit in the coadd") 

172 self.ccdSchema = afwTable.ExposureTable.makeMinimalSchema() 

173 self.ccdCcdKey = self.ccdSchema.addField("ccd", type=numpy.int32, doc="cameraGeom CCD serial number") 

174 self.ccdVisitKey = self.ccdSchema.addField("visit", type=numpy.int64, 

175 doc="Foreign key for the visits (coaddTempExp) catalog") 

176 self.ccdGoodPixKey = self.ccdSchema.addField("goodpix", type=numpy.int32, 

177 doc="Number of good pixels in this CCD") 

178 if self.config.saveCcdWeights: 

179 self.ccdWeightKey = self.ccdSchema.addField("weight", type=float, 

180 doc="Weight for this visit in the coadd") 

181 self.visitFilterKey = self.visitSchema.addField("filter", type=str, size=32, 

182 doc="Physical filter associated with this visit.") 

183 self.ccdFilterKey = self.ccdSchema.addField("filter", type=str, size=32, 

184 doc="Physical filter associated with this visit.") 

185 

186 def makeCoaddTempExpRecorder(self, visitId, num=0): 

187 """Return a CoaddTempExpInputRecorder instance to help with saving a CoaddTempExp's inputs. 

188 

189 The visitId may be any number that is unique for each CoaddTempExp that goes into a coadd, 

190 but ideally should be something more meaningful that can be used to reconstruct a data ID. 

191 """ 

192 return CoaddTempExpInputRecorder(self, visitId, num=num) 

193 

194 def makeCoaddInputs(self): 

195 """Create a CoaddInputs object with schemas defined by the task configuration""" 

196 return afwImage.CoaddInputs(self.visitSchema, self.ccdSchema) 

197 

198 def addVisitToCoadd(self, coaddInputs, coaddTempExp, weight): 

199 """Called by AssembleCoaddTask when adding (a subset of) a coaddTempExp to a coadd. The 

200 base class impementation extracts the CoaddInputs from the coaddTempExp and appends 

201 them to the given coaddInputs, filling in the weight column(s). 

202 

203 Note that the passed coaddTempExp may be a subimage, but that this method will only be 

204 called for the first subimage 

205 

206 Returns the record for the visit to allow subclasses to fill in additional fields. 

207 Warns and returns None if the inputRecorder catalogs for the coaddTempExp are not usable. 

208 """ 

209 tempExpInputs = coaddTempExp.getInfo().getCoaddInputs() 

210 if len(tempExpInputs.visits) != 1: 

211 self.log.warning("CoaddInputs for coaddTempExp should have exactly one record in visits table " 

212 "(found %d). CoaddInputs for this visit will not be saved.", 

213 len(tempExpInputs.visits)) 

214 return None 

215 inputVisitRecord = tempExpInputs.visits[0] 

216 outputVisitRecord = coaddInputs.visits.addNew() 

217 outputVisitRecord.assign(inputVisitRecord) 

218 outputVisitRecord.setD(self.visitWeightKey, weight) 

219 outputVisitRecord.set(self.visitFilterKey, coaddTempExp.getFilter().physicalLabel) 

220 for inputCcdRecord in tempExpInputs.ccds: 

221 if inputCcdRecord.getL(self.ccdVisitKey) != inputVisitRecord.getId(): 

222 self.log.warning("CoaddInputs for coaddTempExp with id %d contains CCDs with visit=%d. " 

223 "CoaddInputs may be unreliable.", 

224 inputVisitRecord.getId(), inputCcdRecord.getL(self.ccdVisitKey)) 

225 outputCcdRecord = coaddInputs.ccds.addNew() 

226 outputCcdRecord.assign(inputCcdRecord) 

227 if self.config.saveCcdWeights: 

228 outputCcdRecord.setD(self.ccdWeightKey, weight) 

229 outputCcdRecord.set(self.ccdFilterKey, coaddTempExp.getFilter().physicalLabel) 

230 return inputVisitRecord