Coverage for python/lsst/meas/astrom/directMatch.py: 28%

52 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-03-23 17:07 -0700

1 

2__all__ = ["DirectMatchConfig", "DirectMatchTask", "DirectMatchConfigWithoutLoader"] 

3 

4from lsst.pex.config import Config, Field, ConfigurableField 

5from lsst.pipe.base import Task, Struct 

6from lsst.meas.algorithms import (LoadIndexedReferenceObjectsTask, ScienceSourceSelectorTask, 

7 ReferenceSourceSelectorTask) 

8import lsst.afw.table as afwTable 

9from lsst.geom import arcseconds, averageSpherePoint 

10 

11 

12class DirectMatchConfigWithoutLoader(Config): 

13 """Configuration for `DirectMatchTask` when an already-initialized 

14 ``refObjLoader`` will be passed to this task. 

15 """ 

16 matchRadius = Field(dtype=float, default=0.25, doc="Matching radius, arcsec") 

17 sourceSelection = ConfigurableField(target=ScienceSourceSelectorTask, 

18 doc="Selection of science sources") 

19 referenceSelection = ConfigurableField(target=ReferenceSourceSelectorTask, 

20 doc="Selection of reference sources") 

21 

22 

23class DirectMatchConfig(DirectMatchConfigWithoutLoader): 

24 """Configuration for `DirectMatchTask`. 

25 """ 

26 refObjLoader = ConfigurableField(target=LoadIndexedReferenceObjectsTask, doc="Load reference objects") 

27 

28 

29class DirectMatchTask(Task): 

30 """Simple, brute force matching of a source catalog to a reference catalog. 

31 

32 Parameters 

33 ---------- 

34 butler : `lsst.daf.persistence.Butler` 

35 Data butler containing the relevant reference catalog data. 

36 refObjLoader : `lsst.meas.algorithms.LoadReferenceObjectsTask` or `None` 

37 For loading reference objects. 

38 **kwargs 

39 Other keyword arguments required for instantiating a Task (such as 

40 ``config``). 

41 """ 

42 ConfigClass = DirectMatchConfig 

43 _DefaultName = "directMatch" 

44 

45 def __init__(self, butler=None, refObjLoader=None, **kwargs): 

46 Task.__init__(self, **kwargs) 

47 if not refObjLoader: 

48 if butler: 

49 if not isinstance(self.config, DirectMatchConfig): 

50 raise RuntimeError("DirectMatchTask must be initialized with DirectMatchConfig " 

51 "if a refObjLoader is not supplied at initialization") 

52 self.makeSubtask("refObjLoader", butler=butler) 

53 else: 

54 self.refObjLoader = None 

55 

56 else: 

57 self.refObjLoader = refObjLoader 

58 self.makeSubtask("sourceSelection") 

59 self.makeSubtask("referenceSelection") 

60 

61 def setRefObjLoader(self, refObjLoader): 

62 """Set the reference object loader for the task. 

63 

64 Parameters 

65 ---------- 

66 refObjLoader 

67 An instance of a reference object loader, either a 

68 `lsst.meas.algorithms.LoadReferenceObjectsTask` task or a 

69 `lsst.meas.algorithms.ReferenceObjectLoader` instance. A task can 

70 be used as a subtask and is generally used in gen2 middleware. The 

71 class is designed to be used with gen3 middleware and is 

72 initialized outside the normal task framework. 

73 """ 

74 self.refObjLoader = refObjLoader 

75 

76 def run(self, catalog, filterName=None, epoch=None): 

77 """Load reference objects and match to them. 

78 

79 Parameters 

80 ---------- 

81 catalog : `lsst.afw.table.SourceCatalog` 

82 Catalog to match. 

83 filterName : `str` 

84 Name of filter loading fluxes. 

85 epoch : `astropy.time.Time` or `None` 

86 Epoch to which to correct proper motion and parallax, or `None` to 

87 not apply such corrections. 

88 

89 Returns 

90 ------- 

91 result : `lsst.pipe.base.Struct` 

92 Result struct with components: 

93 

94 ``matches`` 

95 Matched sources with associated reference 

96 (`lsst.afw.table.SourceMatchVector`). 

97 ``matchMeta`` 

98 Match metadata (`lsst.meas.astrom.MatchMetadata`). 

99 """ 

100 if self.refObjLoader is None: 

101 raise RuntimeError("Running matcher task with no refObjLoader set in __ini__ or setRefObjLoader") 

102 circle = self.calculateCircle(catalog) 

103 matchMeta = self.refObjLoader.getMetadataCircle(circle.center, circle.radius, filterName, epoch=epoch) 

104 emptyResult = Struct(matches=[], matchMeta=matchMeta) 

105 sourceSelection = self.sourceSelection.run(catalog) 

106 if len(sourceSelection.sourceCat) == 0: 

107 self.log.warning("No objects selected from %d objects in source catalog", len(catalog)) 

108 return emptyResult 

109 refData = self.refObjLoader.loadSkyCircle(circle.center, circle.radius, filterName, epoch=epoch) 

110 refCat = refData.refCat 

111 refSelection = self.referenceSelection.run(refCat) 

112 if len(refSelection.sourceCat) == 0: 

113 self.log.warning("No objects selected from %d objects in reference catalog", len(refCat)) 

114 return emptyResult 

115 matches = afwTable.matchRaDec(refSelection.sourceCat, sourceSelection.sourceCat, 

116 self.config.matchRadius*arcseconds) 

117 self.log.info("Matched %d from %d/%d input and %d/%d reference sources", 

118 len(matches), len(sourceSelection.sourceCat), len(catalog), 

119 len(refSelection.sourceCat), len(refCat)) 

120 return Struct(matches=matches, matchMeta=matchMeta, refCat=refCat, sourceSelection=sourceSelection, 

121 refSelection=refSelection) 

122 

123 def calculateCircle(self, catalog): 

124 """Calculate a circle enclosing the catalog. 

125 

126 Parameters 

127 ---------- 

128 catalog : `lsst.afw.table.SourceCatalog` 

129 Catalog to encircle. 

130 

131 Returns 

132 ------- 

133 result : `lsst.pipe.base.Struct` 

134 Result struct with components: 

135 

136 ``center`` 

137 ICRS center coordinate (`lsst.afw.geom.SpherePoint`). 

138 ``radius`` 

139 Radius of the circle (`lsst.geom.Angle`). 

140 """ 

141 coordList = [src.getCoord() for src in catalog] 

142 center = averageSpherePoint(coordList) 

143 radius = max(center.separation(coord) for coord in coordList) 

144 return Struct(center=center, radius=radius + self.config.matchRadius*arcseconds)