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

52 statements  

« prev     ^ index     » next       coverage.py v7.2.5, created at 2023-05-09 02:38 -0700

1 

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

3 

4import warnings 

5 

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

7from lsst.pipe.base import Task, Struct 

8from lsst.meas.algorithms import (LoadReferenceObjectsTask, 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 = ConfigurableField(target=LoadReferenceObjectsTask, doc="Load reference objects") 

29 

30 

31class DirectMatchTask(Task): 

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

33 

34 Parameters 

35 ---------- 

36 butler : `None` 

37 Compatibility parameter. Should not be used. 

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

39 For loading reference objects. 

40 **kwargs 

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

42 ``config``). 

43 """ 

44 ConfigClass = DirectMatchConfig 

45 _DefaultName = "directMatch" 

46 

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

48 Task.__init__(self, **kwargs) 

49 if not refObjLoader: 

50 if butler: 

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

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

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

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

55 category=FutureWarning, stacklevel=2) 

56 self.refObjLoader = refObjLoader 

57 self.makeSubtask("sourceSelection") 

58 self.makeSubtask("referenceSelection") 

59 

60 def setRefObjLoader(self, refObjLoader): 

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

62 

63 Parameters 

64 ---------- 

65 refObjLoader : `lsst.meas.algorithms.ReferenceObjectLoader` 

66 An instance of a reference object loader. 

67 """ 

68 self.refObjLoader = refObjLoader 

69 

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

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

72 

73 Parameters 

74 ---------- 

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

76 Catalog to match. 

77 filterName : `str` 

78 Name of filter loading fluxes. 

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

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

81 not apply such corrections. 

82 

83 Returns 

84 ------- 

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

86 Result struct with components: 

87 

88 ``matches`` 

89 Matched sources with associated reference 

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

91 ``matchMeta`` 

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

93 """ 

94 if self.refObjLoader is None: 

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

96 circle = self.calculateCircle(catalog) 

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

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

99 sourceSelection = self.sourceSelection.run(catalog) 

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

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

102 return emptyResult 

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

104 refCat = refData.refCat 

105 refSelection = self.referenceSelection.run(refCat) 

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

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

108 return emptyResult 

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

110 self.config.matchRadius*arcseconds) 

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

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

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

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

115 refSelection=refSelection) 

116 

117 def calculateCircle(self, catalog): 

118 """Calculate a circle enclosing the catalog. 

119 

120 Parameters 

121 ---------- 

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

123 Catalog to encircle. 

124 

125 Returns 

126 ------- 

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

128 Result struct with components: 

129 

130 ``center`` 

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

132 ``radius`` 

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

134 """ 

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

136 center = averageSpherePoint(coordList) 

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

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