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

52 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-07-21 11:04 +0000

1 

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

3 

4import warnings 

5 

6from lsst.pex.config import Config, Field, ConfigurableField, ConfigField 

7from lsst.pipe.base import Task, Struct 

8from lsst.meas.algorithms import (LoadReferenceObjectsConfig, ScienceSourceSelectorTask, 

9 ReferenceSourceSelectorTask) 

10import lsst.afw.table as afwTable 

11from lsst.geom import arcseconds, averageSpherePoint 

12 

13 

14class DirectMatchConfigWithoutLoader(Config): 

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

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

17 """ 

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

19 sourceSelection = ConfigurableField(target=ScienceSourceSelectorTask, 

20 doc="Selection of science sources") 

21 referenceSelection = ConfigurableField(target=ReferenceSourceSelectorTask, 

22 doc="Selection of reference sources") 

23 

24 

25class DirectMatchConfig(DirectMatchConfigWithoutLoader): 

26 """Configuration for `DirectMatchTask`. 

27 """ 

28 refObjLoader = ConfigField(dtype=LoadReferenceObjectsConfig, 

29 doc="Configuration of reference object loader") 

30 

31 

32class DirectMatchTask(Task): 

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

34 

35 Parameters 

36 ---------- 

37 butler : `None` 

38 Compatibility parameter. Should not be used. 

39 refObjLoader : `lsst.meas.algorithms.ReferenceObjectLoader` or `None` 

40 For loading reference objects. 

41 **kwargs 

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

43 ``config``). 

44 """ 

45 ConfigClass = DirectMatchConfig 

46 _DefaultName = "directMatch" 

47 

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

49 Task.__init__(self, **kwargs) 

50 if not refObjLoader: 

51 if butler: 

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

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

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

55 warnings.warn("The 'butler' parameter is no longer used and can be safely removed.", 

56 category=FutureWarning, stacklevel=2) 

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 : `lsst.meas.algorithms.ReferenceObjectLoader` 

67 An instance of a reference object loader. 

68 """ 

69 self.refObjLoader = refObjLoader 

70 

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

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

73 

74 Parameters 

75 ---------- 

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

77 Catalog to match. 

78 filterName : `str` 

79 Name of filter loading fluxes. 

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

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

82 not apply such corrections. 

83 

84 Returns 

85 ------- 

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

87 Result struct with components: 

88 

89 ``matches`` 

90 Matched sources with associated reference 

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

92 ``matchMeta`` 

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

94 """ 

95 if self.refObjLoader is None: 

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

97 circle = self.calculateCircle(catalog) 

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

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

100 sourceSelection = self.sourceSelection.run(catalog) 

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

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

103 return emptyResult 

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

105 refCat = refData.refCat 

106 refSelection = self.referenceSelection.run(refCat) 

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

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

109 return emptyResult 

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

111 self.config.matchRadius*arcseconds) 

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

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

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

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

116 refSelection=refSelection) 

117 

118 def calculateCircle(self, catalog): 

119 """Calculate a circle enclosing the catalog. 

120 

121 Parameters 

122 ---------- 

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

124 Catalog to encircle. 

125 

126 Returns 

127 ------- 

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

129 Result struct with components: 

130 

131 ``center`` 

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

133 ``radius`` 

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

135 """ 

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

137 center = averageSpherePoint(coordList) 

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

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