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

75 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-04 04:14 -0700

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 ( 

32 CountAction, 

33 FracThreshold, 

34 MedianAction, 

35 SigmaMadAction, 

36 StdevAction, 

37) 

38from ..actions.vector import ( 

39 BandSelector, 

40 CalcSn, 

41 ConvertFluxToMag, 

42 LoadVector, 

43 MultiCriteriaDownselectVector, 

44 PerGroupStatistic, 

45 RangeSelector, 

46 ResidualWithPerGroupStatistic, 

47 SnSelector, 

48 ThresholdSelector, 

49) 

50from ..interfaces import AnalysisTool 

51 

52 

53class StellarPhotometricRepeatability(AnalysisTool): 

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

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

56 the individual source measurements are grouped together by object index 

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

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

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

60 computed for the filtered groups. 

61 """ 

62 

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

64 PA2Value = Field[float]( 

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

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

67 default=15.0, 

68 ) 

69 

70 def setDefaults(self): 

71 super().setDefaults() 

72 

73 # Apply per-source selection criteria 

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

75 

76 # Compute per-group quantities 

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

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

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

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

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

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

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

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

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

86 # Use mmag units 

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

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

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

90 returnMillimags=True, 

91 ) 

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

93 

94 # Filter on per-group quantities 

95 self.process.filterActions.perGroupStdevFiltered = MultiCriteriaDownselectVector( 

96 vectorKey="perGroupStdev" 

97 ) 

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

99 vectorKey="perGroupCount", 

100 op="ge", 

101 threshold=3, 

102 ) 

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

104 vectorKey="perGroupSn", 

105 minimum=200, 

106 ) 

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

108 vectorKey="perGroupExtendedness", 

109 op="le", 

110 threshold=0.5, 

111 ) 

112 

113 # Compute summary statistics on filtered groups 

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

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

116 

117 self.produce.plot = HistPlot() 

118 

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

120 

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

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

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

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

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

126 

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

128 

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

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

131 

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

133 "photRepeatStdev": "mmag", 

134 "photRepeatOutlier": "percent", 

135 "photRepeatNsources": "ct", 

136 } 

137 

138 def finalize(self): 

139 super().finalize() 

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

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

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

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

144 returnMillimags=True, 

145 ) 

146 self.process.calculateActions.photRepeatOutlier = FracThreshold( 

147 vectorKey="perGroupStdevFiltered", 

148 op="ge", 

149 threshold=self.PA2Value, 

150 percent=True, 

151 relative_to_median=True, 

152 ) 

153 

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

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

156 

157 self.produce.metric.newNames = { 

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

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

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

161 } 

162 

163 

164class StellarPhotometricResidualsFocalPlane(AnalysisTool): 

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

166 focal plane. 

167 

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

169 individual source measurements are grouped together by object index 

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

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

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

173 """ 

174 

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

176 

177 def setDefaults(self): 

178 super().setDefaults() 

179 

180 # Apply per-source selection criteria 

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

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

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

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

185 

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

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

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

189 returnMillimags=True, 

190 ) 

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

192 

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

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

195 

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

197 

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

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

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

201 

202 self.process.calculateActions.photResidTractMedian = MedianAction(vectorKey="z") 

203 self.process.calculateActions.photResidTractStdev = StdevAction(vectorKey="z") 

204 self.process.calculateActions.photResidTractSigmaMad = SigmaMadAction(vectorKey="z") 

205 

206 self.produce.plot = FocalPlanePlot() 

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

208 

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

210 "photResidTractSigmaMad": "mmag", 

211 "photResidTractStdev": "mmag", 

212 "photResidTractMedian": "mmag", 

213 } 

214 

215 self.produce.metric.newNames = { 

216 "photResidTractSigmaMad": "{band}_photResidTractSigmaMad", 

217 "photResidTractStdev": "{band}_photResidTractStdev", 

218 "photResidTractMedian": "{band}_photResidTractMedian", 

219 }