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

1import lsst.pipe.base as pipeBase 

2import lsst.pex.config as pexConfig 

3import lsst.geom as geom 

4import numpy as np 

5 

6from lsst.faro.utils.matcher import matchCatalogs 

7 

8__all__ = ('MatchedBaseTaskConnections', 'MatchedBaseTaskConfig', 'MatchedBaseTask', 'MatchedTractBaseTask') 

9 

10 

11class MatchedBaseTaskConnections(pipeBase.PipelineTaskConnections, 

12 dimensions=(), 

13 defaultTemplates={"coaddName": "deep", 

14 "photoCalibName": "calexp.photoCalib", 

15 "wcsName": "calexp.wcs", 

16 "externalPhotoCalibName": "fgcm", 

17 "externalWcsName": "jointcal"}): 

18 sourceCatalogs = pipeBase.connectionTypes.Input(doc="Source catalogs to match up.", 

19 dimensions=("instrument", "visit", 

20 "detector", "band"), 

21 storageClass="SourceCatalog", 

22 name="src", 

23 multiple=True) 

24 photoCalibs = pipeBase.connectionTypes.Input(doc="Photometric calibration object.", 

25 dimensions=("instrument", "visit", 

26 "detector", "band"), 

27 storageClass="PhotoCalib", 

28 name="{photoCalibName}", 

29 multiple=True) 

30 astromCalibs = pipeBase.connectionTypes.Input(doc="WCS for the catalog.", 

31 dimensions=("instrument", "visit", 

32 "detector", "band"), 

33 storageClass="Wcs", 

34 name="{wcsName}", 

35 multiple=True) 

36 externalSkyWcsTractCatalog = pipeBase.connectionTypes.Input( 

37 doc=("Per-tract, per-visit wcs calibrations. These catalogs use the detector " 

38 "id for the catalog id, sorted on id for fast lookup."), 

39 name="{externalWcsName}SkyWcsCatalog", 

40 storageClass="ExposureCatalog", 

41 dimensions=("instrument", "visit", "tract", "band"), 

42 multiple=True 

43 ) 

44 externalSkyWcsGlobalCatalog = pipeBase.connectionTypes.Input( 

45 doc=("Per-visit wcs calibrations computed globally (with no tract information). " 

46 "These catalogs use the detector id for the catalog id, sorted on id for " 

47 "fast lookup."), 

48 name="{externalWcsName}SkyWcsCatalog", 

49 storageClass="ExposureCatalog", 

50 dimensions=("instrument", "visit", "band"), 

51 multiple=True 

52 ) 

53 externalPhotoCalibTractCatalog = pipeBase.connectionTypes.Input( 

54 doc=("Per-tract, per-visit photometric calibrations. These catalogs use the " 

55 "detector id for the catalog id, sorted on id for fast lookup."), 

56 name="{externalPhotoCalibName}PhotoCalibCatalog", 

57 storageClass="ExposureCatalog", 

58 dimensions=("instrument", "visit", "tract", "band"), 

59 multiple=True 

60 ) 

61 externalPhotoCalibGlobalCatalog = pipeBase.connectionTypes.Input( 

62 doc=("Per-visit photometric calibrations computed globally (with no tract " 

63 "information). These catalogs use the detector id for the catalog id, " 

64 "sorted on id for fast lookup."), 

65 name="{externalPhotoCalibName}PhotoCalibCatalog", 

66 storageClass="ExposureCatalog", 

67 dimensions=("instrument", "visit", "band"), 

68 multiple=True 

69 ) 

70 skyMap = pipeBase.connectionTypes.Input( 

71 doc="Input definition of geometry/bbox and projection/wcs for warped exposures", 

72 name="skyMap", 

73 storageClass="SkyMap", 

74 dimensions=("skymap",), 

75 ) 

76 

77 def __init__(self, *, config=None): 

78 super().__init__(config=config) 

79 if config.doApplyExternalSkyWcs: 

80 if config.useGlobalExternalSkyWcs: 

81 self.inputs.remove("externalSkyWcsTractCatalog") 

82 else: 

83 self.inputs.remove("externalSkyWcsGlobalCatalog") 

84 else: 

85 self.inputs.remove("externalSkyWcsTractCatalog") 

86 self.inputs.remove("externalSkyWcsGlobalCatalog") 

87 if config.doApplyExternalPhotoCalib: 

88 if config.useGlobalExternalPhotoCalib: 

89 self.inputs.remove("externalPhotoCalibTractCatalog") 

90 else: 

91 self.inputs.remove("externalPhotoCalibGlobalCatalog") 

92 else: 

93 self.inputs.remove("externalPhotoCalibTractCatalog") 

94 self.inputs.remove("externalPhotoCalibGlobalCatalog") 

95 

96 

97class MatchedBaseTaskConfig(pipeBase.PipelineTaskConfig, 

98 pipelineConnections=MatchedBaseTaskConnections): 

99 match_radius = pexConfig.Field(doc="Match radius in arcseconds.", dtype=float, default=1) 

100 doApplyExternalSkyWcs = pexConfig.Field(doc="Whether or not to use the external wcs.", 

101 dtype=bool, default=False) 

102 useGlobalExternalSkyWcs = pexConfig.Field(doc="Whether or not to use the global external wcs.", 

103 dtype=bool, default=False) 

104 doApplyExternalPhotoCalib = pexConfig.Field(doc="Whether or not to use the external photoCalib.", 

105 dtype=bool, default=False) 

106 useGlobalExternalPhotoCalib = pexConfig.Field(doc="Whether or not to use the global external photoCalib.", 

107 dtype=bool, default=False) 

108 

109 

110class MatchedBaseTask(pipeBase.PipelineTask): 

111 

112 ConfigClass = MatchedBaseTaskConfig 

113 _DefaultName = "matchedBaseTask" 

114 

115 def __init__(self, config: MatchedBaseTaskConfig, *args, **kwargs): 

116 super().__init__(*args, config=config, **kwargs) 

117 self.radius = self.config.match_radius 

118 self.level = "patch" 

119 

120 def run(self, sourceCatalogs, photoCalibs, astromCalibs, dataIds, wcs, box, 

121 doApplyExternalSkyWcs=False, doApplyExternalPhotoCalib=False): 

122 self.log.info("Running catalog matching") 

123 radius = geom.Angle(self.radius, geom.arcseconds) 

124 srcvis, matched = matchCatalogs(sourceCatalogs, photoCalibs, astromCalibs, dataIds, radius, 

125 logger=self.log) 

126 # Trim the output to the patch bounding box 

127 out_matched = type(matched)(matched.schema) 

128 self.log.info("%s sources in matched catalog.", len(matched)) 

129 for record in matched: 

130 if box.contains(wcs.skyToPixel(record.getCoord())): 

131 out_matched.append(record) 

132 self.log.info("%s sources when trimmed to %s boundaries.", len(out_matched), self.level) 

133 return pipeBase.Struct(outputCatalog=out_matched) 

134 

135 def get_box_wcs(self, skymap, oid): 

136 tract_info = skymap.generateTract(oid['tract']) 

137 wcs = tract_info.getWcs() 

138 patch_info = tract_info.getPatchInfo(oid['patch']) 

139 patch_box = patch_info.getInnerBBox() 

140 self.log.info("Running tract: %s and patch: %s", oid['tract'], oid['patch']) 

141 return patch_box, wcs 

142 

143 def runQuantum(self, butlerQC, 

144 inputRefs, 

145 outputRefs): 

146 inputs = butlerQC.get(inputRefs) 

147 oid = outputRefs.outputCatalog.dataId.byName() 

148 skymap = inputs['skyMap'] 

149 del inputs['skyMap'] 

150 box, wcs = self.get_box_wcs(skymap, oid) 

151 # Cast to float to handle fractional pixels 

152 box = geom.Box2D(box) 

153 inputs['dataIds'] = [butlerQC.registry.expandDataId(el.dataId) for el in inputRefs.sourceCatalogs] 

154 inputs['wcs'] = wcs 

155 inputs['box'] = box 

156 inputs['doApplyExternalSkyWcs'] = self.config.doApplyExternalSkyWcs 

157 inputs['doApplyExternalPhotoCalib'] = self.config.doApplyExternalPhotoCalib 

158 

159 if self.config.doApplyExternalPhotoCalib: 

160 if self.config.useGlobalExternalPhotoCalib: 

161 externalPhotoCalibCatalog = inputs.pop('externalPhotoCalibGlobalCatalog') 

162 else: 

163 externalPhotoCalibCatalog = inputs.pop('externalPhotoCalibTractCatalog') 

164 

165 flatPhotoCalibList = np.hstack(externalPhotoCalibCatalog) 

166 visitPhotoCalibList = np.array([calib['visit'] for calib in flatPhotoCalibList]) 

167 detectorPhotoCalibList = np.array([calib['id'] for calib in flatPhotoCalibList]) 

168 

169 if self.config.doApplyExternalSkyWcs: 

170 if self.config.useGlobalExternalSkyWcs: 

171 externalSkyWcsCatalog = inputs.pop('externalSkyWcsGlobalCatalog') 

172 else: 

173 externalSkyWcsCatalog = inputs.pop('externalSkyWcsTractCatalog') 

174 

175 flatSkyWcsList = np.hstack(externalSkyWcsCatalog) 

176 visitSkyWcsList = np.array([calib['visit'] for calib in flatSkyWcsList]) 

177 detectorSkyWcsList = np.array([calib['id'] for calib in flatSkyWcsList]) 

178 

179 if self.config.doApplyExternalPhotoCalib: 

180 for i in range(len(inputs['dataIds'])): 

181 dataId = inputs['dataIds'][i] 

182 detector = dataId['detector'] 

183 visit = dataId['visit'] 

184 calib_find = (visitPhotoCalibList == visit) & (detectorPhotoCalibList == detector) 

185 row = flatPhotoCalibList[calib_find] 

186 externalPhotoCalib = row[0].getPhotoCalib() 

187 inputs['photoCalibs'][i] = externalPhotoCalib 

188 

189 if self.config.doApplyExternalSkyWcs: 

190 for i in range(len(inputs['dataIds'])): 

191 dataId = inputs['dataIds'][i] 

192 detector = dataId['detector'] 

193 visit = dataId['visit'] 

194 calib_find = (visitSkyWcsList == visit) & (detectorSkyWcsList == detector) 

195 row = flatSkyWcsList[calib_find] 

196 externalSkyWcs = row[0].getWcs() 

197 inputs['astromCalibs'][i] = externalSkyWcs 

198 

199 outputs = self.run(**inputs) 

200 butlerQC.put(outputs, outputRefs) 

201 

202 

203class MatchedTractBaseTask(MatchedBaseTask): 

204 

205 ConfigClass = MatchedBaseTaskConfig 

206 _DefaultName = "matchedTractBaseTask" 

207 

208 def __init__(self, config: MatchedBaseTaskConfig, *args, **kwargs): 

209 super().__init__(*args, config=config, **kwargs) 

210 self.radius = self.config.match_radius 

211 self.level = "tract" 

212 

213 def get_box_wcs(self, skymap, oid): 

214 tract_info = skymap.generateTract(oid['tract']) 

215 wcs = tract_info.getWcs() 

216 tract_box = tract_info.getBBox() 

217 self.log.info("Running tract: %s", oid['tract']) 

218 return tract_box, wcs