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

46 statements  

« prev     ^ index     » next       coverage.py v7.5.1, created at 2024-05-07 03:19 -0700

1 

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

3 

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

5from lsst.pipe.base import Task, Struct 

6from lsst.meas.algorithms import (LoadReferenceObjectsConfig, 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 = ConfigField(dtype=LoadReferenceObjectsConfig, 

27 doc="Configuration of reference object loader") 

28 

29 

30class DirectMatchTask(Task): 

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

32 

33 Parameters 

34 ---------- 

35 butler : `None` 

36 Compatibility parameter. Should not be used. 

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

38 A reference object loader object; gen3 pipeline tasks will pass `None` 

39 and call `setRefObjLoader` in `runQuantum`. 

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, refObjLoader=None, **kwargs): 

48 Task.__init__(self, **kwargs) 

49 self.refObjLoader = refObjLoader 

50 self.makeSubtask("sourceSelection") 

51 self.makeSubtask("referenceSelection") 

52 

53 def setRefObjLoader(self, refObjLoader): 

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

55 

56 Parameters 

57 ---------- 

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

59 An instance of a reference object loader. 

60 """ 

61 self.refObjLoader = refObjLoader 

62 

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

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

65 

66 Parameters 

67 ---------- 

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

69 Catalog to match. 

70 filterName : `str` 

71 Name of filter loading fluxes. 

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

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

74 not apply such corrections. 

75 

76 Returns 

77 ------- 

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

79 Result struct with components: 

80 

81 ``matches`` 

82 Matched sources with associated reference 

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

84 ``matchMeta`` 

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

86 """ 

87 if self.refObjLoader is None: 

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

89 circle = self.calculateCircle(catalog) 

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

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

92 sourceSelection = self.sourceSelection.run(catalog) 

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

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

95 return emptyResult 

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

97 refCat = refData.refCat 

98 refSelection = self.referenceSelection.run(refCat) 

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

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

101 return emptyResult 

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

103 self.config.matchRadius*arcseconds) 

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

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

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

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

108 refSelection=refSelection) 

109 

110 def calculateCircle(self, catalog): 

111 """Calculate a circle enclosing the catalog. 

112 

113 Parameters 

114 ---------- 

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

116 Catalog to encircle. 

117 

118 Returns 

119 ------- 

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

121 Result struct with components: 

122 

123 ``center`` 

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

125 ``radius`` 

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

127 """ 

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

129 center = averageSpherePoint(coordList) 

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

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