Coverage for python/lsst/analysis/tools/atools/photometricRepeatability.py: 20%

72 statements  

« prev     ^ index     » next       coverage.py v7.4.0, created at 2024-01-20 13:15 +0000

1# This file is part of analysis_tools. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

8# 

9# This program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with this program. If not, see <https://www.gnu.org/licenses/>. 

21from __future__ import annotations 

22 

23__all__ = ( 

24 "StellarPhotometricRepeatability", 

25 "StellarPhotometricResidualsFocalPlane", 

26) 

27 

28from lsst.pex.config import Field 

29 

30from ..actions.plot import FocalPlanePlot, HistPanel, HistPlot, HistStatsPanel 

31from ..actions.scalar.scalarActions import CountAction, FracThreshold, MedianAction 

32from ..actions.vector import ( 

33 BandSelector, 

34 CalcSn, 

35 ConvertFluxToMag, 

36 LoadVector, 

37 MultiCriteriaDownselectVector, 

38 PerGroupStatistic, 

39 RangeSelector, 

40 ResidualWithPerGroupStatistic, 

41 SnSelector, 

42 ThresholdSelector, 

43) 

44from ..interfaces import AnalysisTool 

45 

46 

47class StellarPhotometricRepeatability(AnalysisTool): 

48 """Compute photometric repeatability from multiple measurements of a set of 

49 stars. First, a set of per-source quality criteria are applied. Second, 

50 the individual source measurements are grouped together by object index 

51 and per-group quantities are computed (e.g., a representative S/N for the 

52 group based on the median of associated per-source measurements). Third, 

53 additional per-group criteria are applied. Fourth, summary statistics are 

54 computed for the filtered groups. 

55 """ 

56 

57 fluxType = Field[str](doc="Flux type to calculate repeatability with", default="psfFlux") 

58 PA2Value = Field[float]( 

59 doc="Used to compute the percent of individual measurements that deviate by more than PA2Value" 

60 "from the mean of each measurement (PF1). Units of PA2Value are mmag.", 

61 default=15.0, 

62 ) 

63 

64 def setDefaults(self): 

65 super().setDefaults() 

66 

67 # Apply per-source selection criteria 

68 self.prep.selectors.bandSelector = BandSelector() 

69 

70 # Compute per-group quantities 

71 self.process.buildActions.perGroupSn = PerGroupStatistic() 

72 self.process.buildActions.perGroupSn.buildAction = CalcSn() 

73 self.process.buildActions.perGroupSn.func = "median" 

74 self.process.buildActions.perGroupExtendedness = PerGroupStatistic() 

75 self.process.buildActions.perGroupExtendedness.buildAction.vectorKey = "extendedness" 

76 self.process.buildActions.perGroupExtendedness.func = "median" 

77 self.process.buildActions.perGroupCount = PerGroupStatistic() 

78 self.process.buildActions.perGroupCount.buildAction.vectorKey = f"{self.fluxType}" 

79 self.process.buildActions.perGroupCount.func = "count" 

80 # Use mmag units 

81 self.process.buildActions.perGroupStdev = PerGroupStatistic() 

82 self.process.buildActions.perGroupStdev.buildAction = ConvertFluxToMag( 

83 vectorKey=f"{self.fluxType}", 

84 returnMillimags=True, 

85 ) 

86 self.process.buildActions.perGroupStdev.func = "std" 

87 

88 # Filter on per-group quantities 

89 self.process.filterActions.perGroupStdevFiltered = MultiCriteriaDownselectVector( 

90 vectorKey="perGroupStdev" 

91 ) 

92 self.process.filterActions.perGroupStdevFiltered.selectors.count = ThresholdSelector( 

93 vectorKey="perGroupCount", 

94 op="ge", 

95 threshold=3, 

96 ) 

97 self.process.filterActions.perGroupStdevFiltered.selectors.sn = RangeSelector( 

98 vectorKey="perGroupSn", 

99 minimum=200, 

100 ) 

101 self.process.filterActions.perGroupStdevFiltered.selectors.extendedness = ThresholdSelector( 

102 vectorKey="perGroupExtendedness", 

103 op="le", 

104 threshold=0.5, 

105 ) 

106 

107 # Compute summary statistics on filtered groups 

108 self.process.calculateActions.photRepeatStdev = MedianAction(vectorKey="perGroupStdevFiltered") 

109 self.process.calculateActions.photRepeatNsources = CountAction(vectorKey="perGroupStdevFiltered") 

110 

111 self.produce.plot = HistPlot() 

112 

113 self.produce.plot.panels["panel_rms"] = HistPanel() 

114 

115 self.produce.plot.panels["panel_rms"].statsPanel = HistStatsPanel() 

116 self.produce.plot.panels["panel_rms"].statsPanel.statsLabels = ["N", "PA1", "PF1 %"] 

117 self.produce.plot.panels["panel_rms"].statsPanel.stat1 = ["photRepeatNsources"] 

118 self.produce.plot.panels["panel_rms"].statsPanel.stat2 = ["photRepeatStdev"] 

119 self.produce.plot.panels["panel_rms"].statsPanel.stat3 = ["photRepeatOutlier"] 

120 

121 self.produce.plot.panels["panel_rms"].refRelativeToMedian = True 

122 

123 self.produce.plot.panels["panel_rms"].label = "rms (mmag)" 

124 self.produce.plot.panels["panel_rms"].hists = dict(perGroupStdevFiltered="Filtered per group rms") 

125 

126 self.produce.metric.units = { # type: ignore 

127 "photRepeatStdev": "mmag", 

128 "photRepeatOutlier": "percent", 

129 "photRepeatNsources": "ct", 

130 } 

131 

132 def finalize(self): 

133 super().finalize() 

134 self.process.buildActions.perGroupSn.buildAction.fluxType = f"{self.fluxType}" 

135 self.process.buildActions.perGroupCount.buildAction.vectorKey = f"{self.fluxType}" 

136 self.process.buildActions.perGroupStdev.buildAction = ConvertFluxToMag( 

137 vectorKey=f"{self.fluxType}", 

138 returnMillimags=True, 

139 ) 

140 self.process.calculateActions.photRepeatOutlier = FracThreshold( 

141 vectorKey="perGroupStdevFiltered", 

142 op="ge", 

143 threshold=self.PA2Value, 

144 percent=True, 

145 relative_to_median=True, 

146 ) 

147 

148 if isinstance(self.produce.plot, HistPlot): 

149 self.produce.plot.panels["panel_rms"].referenceValue = self.PA2Value 

150 

151 self.produce.metric.newNames = { 

152 "photRepeatStdev": "{band}_stellarPhotRepeatStdev", 

153 "photRepeatOutlier": "{band}_stellarPhotRepeatOutlierFraction", 

154 "photRepeatNsources": "{band}_ct", 

155 } 

156 

157 

158class StellarPhotometricResidualsFocalPlane(AnalysisTool): 

159 """Plot mean photometric residuals as a function of the position on the 

160 focal plane. 

161 

162 First, a set of per-source quality criteria are applied. Second, the 

163 individual source measurements are grouped together by object index 

164 and the per-group magnitude is computed. The residuals between the 

165 individual sources and these magnitudes are then used to construct a plot 

166 showing the mean residual as a function of the focal-plane position. 

167 """ 

168 

169 fluxType = Field[str](doc="Flux type to calculate repeatability with", default="psfFlux") 

170 

171 def setDefaults(self): 

172 super().setDefaults() 

173 

174 # Apply per-source selection criteria 

175 self.prep.selectors.bandSelector = BandSelector() 

176 self.prep.selectors.snSelector = SnSelector() 

177 self.prep.selectors.snSelector.fluxType = "psfFlux" 

178 self.prep.selectors.snSelector.threshold = 50 

179 

180 self.process.buildActions.z = ResidualWithPerGroupStatistic() 

181 self.process.buildActions.z.buildAction = ConvertFluxToMag( 

182 vectorKey=f"{self.fluxType}", 

183 returnMillimags=True, 

184 ) 

185 self.process.buildActions.z.func = "median" 

186 

187 self.process.buildActions.x = LoadVector(vectorKey="x") 

188 self.process.buildActions.y = LoadVector(vectorKey="y") 

189 

190 self.process.buildActions.detector = LoadVector(vectorKey="detector") 

191 

192 self.process.buildActions.statMask = SnSelector() 

193 self.process.buildActions.statMask.threshold = 200 

194 self.process.buildActions.statMask.fluxType = "psfFlux" 

195 

196 self.produce.plot = FocalPlanePlot() 

197 self.produce.plot.xAxisLabel = "x (focal plane)" 

198 self.produce.plot.yAxisLabel = "y (focal plane)" 

199 self.produce.plot.zAxisLabel = "Mag - Mag$_{mean}$ (mmag)"