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 

4 

5from lsst.faro.utils.matcher import match_catalogs 

6 

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

8 

9 

10class MatchedBaseTaskConnections(pipeBase.PipelineTaskConnections, 

11 dimensions=(), 

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

13 "photoCalibName": "calexp.photoCalib", 

14 "wcsName": "calexp.wcs"}): 

15 source_catalogs = pipeBase.connectionTypes.Input(doc="Source catalogs to match up.", 

16 dimensions=("instrument", "visit", 

17 "detector", "band"), 

18 storageClass="SourceCatalog", 

19 name="src", 

20 multiple=True) 

21 photo_calibs = pipeBase.connectionTypes.Input(doc="Photometric calibration object.", 

22 dimensions=("instrument", "visit", 

23 "detector", "band"), 

24 storageClass="PhotoCalib", 

25 name="{photoCalibName}", 

26 multiple=True) 

27 astrom_calibs = pipeBase.connectionTypes.Input(doc="WCS for the catalog.", 

28 dimensions=("instrument", "visit", 

29 "detector", "band"), 

30 storageClass="Wcs", 

31 name="{wcsName}", 

32 multiple=True) 

33 skyMap = pipeBase.connectionTypes.Input( 

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

35 name="skyMap", 

36 storageClass="SkyMap", 

37 dimensions=("skymap",), 

38 ) 

39 

40 # Hack, this is the only way to get a connection without fixed dims 

41 # Inspired by: 

42 # https://github.com/lsst/verify/blob/4816a2c/python/lsst/verify/tasks/metadataMetricTask.py#L65-L101 

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

44 """Customize connection for the astrometric calibrations 

45 

46 Parameters 

47 ---------- 

48 config : `MatchedBaseTaskConfig` 

49 A config for `MatchedBaseTask` or one of its subclasses 

50 """ 

51 super().__init__(config=config) 

52 if config and config.wcsDimensions != self.astrom_calibs.dimensions: 

53 new_astrom_calibs = pipeBase.connectionTypes.Input( 

54 doc=self.astrom_calibs.doc, 

55 dimensions=config.wcsDimensions, 

56 storageClass=self.astrom_calibs.storageClass, 

57 name=self.astrom_calibs.name, 

58 multiple=self.astrom_calibs.multiple 

59 ) 

60 self.astrom_calibs = new_astrom_calibs 

61 self.allConnections['astrom_calibs'] = self.astrom_calibs 

62 

63 

64class MatchedBaseTaskConfig(pipeBase.PipelineTaskConfig, 

65 pipelineConnections=MatchedBaseTaskConnections): 

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

67 apply_external_wcs = pexConfig.Field(doc="Apply correction to coordinates with e.g. a jointcal WCS.", 

68 dtype=bool, default=False) 

69 wcsDimensions = pexConfig.ListField(doc="Override the dimensions of the astrometric calibration objects", 

70 dtype=str, 

71 default=MatchedBaseTaskConnections.astrom_calibs.dimensions) 

72 

73 

74class MatchedBaseTask(pipeBase.PipelineTask): 

75 

76 ConfigClass = MatchedBaseTaskConfig 

77 _DefaultName = "matchedBaseTask" 

78 

79 def __init__(self, config: pipeBase.PipelineTaskConfig, *args, **kwargs): 

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

81 self.radius = self.config.match_radius 

82 self.level = "patch" 

83 

84 def run(self, source_catalogs, photo_calibs, astrom_calibs, vIds, wcs, box, apply_external_wcs): 

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

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

87 srcvis, matched = match_catalogs(source_catalogs, photo_calibs, astrom_calibs, vIds, radius, 

88 apply_external_wcs, logger=self.log) 

89 # Trim the output to the patch bounding box 

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

91 self.log.info(f"{len(matched)} sources in matched catalog.") 

92 for record in matched: 

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

94 out_matched.append(record) 

95 self.log.info(f"{len(out_matched)} sources when trimmed to {self.level} boundaries.") 

96 return pipeBase.Struct(outputCatalog=out_matched) 

97 

98 def get_box_wcs(self, skymap, oid): 

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

100 wcs = tract_info.getWcs() 

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

102 patch_box = patch_info.getInnerBBox() 

103 self.log.info(f"Running tract: {oid['tract']} and patch: {oid['patch']}") 

104 return patch_box, wcs 

105 

106 def runQuantum(self, butlerQC, 

107 inputRefs, 

108 outputRefs): 

109 inputs = butlerQC.get(inputRefs) 

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

111 skymap = inputs['skyMap'] 

112 del inputs['skyMap'] 

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

114 # Cast to float to handle fractional pixels 

115 box = geom.Box2D(box) 

116 inputs['vIds'] = [butlerQC.registry.expandDataId(el.dataId) for el in inputRefs.source_catalogs] 

117 inputs['wcs'] = wcs 

118 inputs['box'] = box 

119 inputs['apply_external_wcs'] = self.config.apply_external_wcs 

120 if inputs['apply_external_wcs'] and not inputs['astrom_calibs']: 

121 self.log.warn('Task configured to apply an external WCS, but no external WCS datasets found.') 

122 if not inputs['astrom_calibs']: # Fill with None if jointcal wcs doesn't exist 

123 inputs['astrom_calibs'] = [None for el in inputs['photo_calibs']] 

124 outputs = self.run(**inputs) 

125 butlerQC.put(outputs, outputRefs) 

126 

127 

128class MatchedTractBaseTask(MatchedBaseTask): 

129 

130 ConfigClass = MatchedBaseTaskConfig 

131 _DefaultName = "matchedTractBaseTask" 

132 

133 def __init__(self, config: pipeBase.PipelineTaskConfig, *args, **kwargs): 

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

135 self.radius = self.config.match_radius 

136 self.level = "tract" 

137 

138 def get_box_wcs(self, skymap, oid): 

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

140 wcs = tract_info.getWcs() 

141 tract_box = tract_info.getBBox() 

142 self.log.info(f"Running tract: {oid['tract']}") 

143 return tract_box, wcs