Coverage for python/lsst/faro/measurement/TractMeasurementTasks.py: 41%

68 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-11 10:52 +0000

1# This file is part of faro. 

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/>. 

21 

22from lsst.afw.table import SourceCatalog 

23from lsst.pex.config import Config, Field 

24from lsst.pipe.base import Struct, Task 

25from lsst.verify import Measurement, Datum 

26 

27from lsst.faro.utils.stellar_locus import stellarLocusResid, calcQuartileClippedStats 

28from lsst.faro.utils.matcher import makeMatchedPhotom 

29from lsst.faro.utils.extinction_corr import extinction_corr 

30from lsst.faro.utils.tex import calculateTEx 

31from lsst.faro.utils.calibrated_catalog import CalibratedCatalog 

32 

33import astropy.units as u 

34import numpy as np 

35from typing import Dict, List 

36 

37__all__ = ("WPerpConfig", "WPerpTask", "TExConfig", "TExTask") 

38 

39 

40class WPerpConfig(Config): 

41 # These are cuts to apply to the r-band only: 

42 bright_rmag_cut = Field( 

43 doc="Bright limit of catalog entries to include", dtype=float, default=17.0 

44 ) 

45 faint_rmag_cut = Field( 

46 doc="Faint limit of catalog entries to include", dtype=float, default=23.0 

47 ) 

48 

49 

50class WPerpTask(Task): 

51 ConfigClass = WPerpConfig 

52 _DefaultName = "WPerpTask" 

53 

54 def run( 

55 self, metricName: str, data: Dict[str, List[CalibratedCatalog]], 

56 ): 

57 self.log.info("Measuring %s", metricName) 

58 bands = set("gri") 

59 

60 if bands.issubset(set(data.keys())): 

61 data = {b: data[b] for b in bands} 

62 rgicatAll = makeMatchedPhotom(data) 

63 magcut = (rgicatAll["base_PsfFlux_mag_r"] < self.config.faint_rmag_cut) & ( 

64 rgicatAll["base_PsfFlux_mag_r"] > self.config.bright_rmag_cut 

65 ) 

66 self.log.info("Merged multiband catalog is %d rows (%d match mag cut)", 

67 len(rgicatAll), np.sum(magcut)) 

68 rgicat = rgicatAll[magcut] 

69 extVals = extinction_corr(rgicat, bands) 

70 

71 wPerp = self.calcWPerp(metricName, rgicat, extVals) 

72 return wPerp 

73 else: 

74 return Struct(measurement=Measurement(metricName, np.nan * u.mmag)) 

75 

76 def calcWPerp(self, metricName: str, phot: SourceCatalog, extinctionVals): 

77 p1, p2, p1coeffs, p2coeffs = stellarLocusResid( 

78 *[phot[f"base_PsfFlux_mag_{b}"] - extinctionVals[f"A_{b}"] for b in 'gri'], 

79 ) 

80 

81 if np.size(p2) > 2: 

82 p2_rms = calcQuartileClippedStats(p2).rms * u.mag 

83 extras = { 

84 "p1_coeffs": Datum( 

85 p1coeffs * u.Unit(""), 

86 label="p1_coefficients", 

87 description="p1 coeffs from wPerp fit", 

88 ), 

89 "p2_coeffs": Datum( 

90 p2coeffs * u.Unit(""), 

91 label="p2_coefficients", 

92 description="p2_coeffs from wPerp fit", 

93 ), 

94 } 

95 

96 return Struct( 

97 measurement=Measurement(metricName, p2_rms.to(u.mmag), extras=extras) 

98 ) 

99 else: 

100 return Struct(measurement=Measurement(metricName, np.nan * u.mmag)) 

101 

102 

103class TExConfig(Config): 

104 minSep = Field( 

105 doc="Inner radius of the annulus in arcmin", dtype=float, default=0.25 

106 ) 

107 maxSep = Field( 

108 doc="Outer radius of the annulus in arcmin", dtype=float, default=1.0 

109 ) 

110 nbins = Field(doc="Number of log-spaced angular bins", dtype=int, default=10) 

111 rhoStat = Field(doc="Rho statistic to be computed", dtype=int, default=1) 

112 shearConvention = Field( 

113 doc="Use shear ellipticity convention rather than distortion", 

114 dtype=bool, 

115 default=False, 

116 ) 

117 columnPsf = Field( 

118 doc="Column to use for PSF model shape moments", 

119 dtype=str, 

120 default="slot_PsfShape", 

121 ) 

122 column = Field( 

123 doc="Column to use for shape moments", dtype=str, default="slot_Shape" 

124 ) 

125 brute = Field( 

126 doc="Run treecorr in brute-force mode for improved reproducibility in tests", 

127 dtype=bool, 

128 default=False 

129 ) 

130 # Eventually want to add option to use only PSF reserve stars 

131 

132 

133class TExTask(Task): 

134 ConfigClass = TExConfig 

135 _DefaultName = "TExTask" 

136 

137 def run( 

138 self, metricName, data: Dict[str, List[CalibratedCatalog]] 

139 ): 

140 bands = data.keys() 

141 if len(bands) != 1: 

142 raise RuntimeError(f'TEx task got bands: {bands} but expecting exactly one') 

143 else: 

144 data = data[list(bands)[0]] 

145 

146 self.log.info("Measuring %s", metricName) 

147 

148 result = calculateTEx(data, self.config) 

149 if "corr" not in result.keys(): 

150 return Struct(measurement=Measurement(metricName, np.nan * u.Unit(""))) 

151 

152 writeExtras = True 

153 if writeExtras: 

154 extras = {} 

155 extras["radius"] = Datum( 

156 result["radius"], label="radius", description="Separation (arcmin)." 

157 ) 

158 extras["corr"] = Datum( 

159 result["corr"], label="Correlation", description="Correlation." 

160 ) 

161 extras["corrErr"] = Datum( 

162 result["corrErr"], 

163 label="Correlation Uncertianty", 

164 description="Correlation Uncertainty.", 

165 ) 

166 else: 

167 extras = None 

168 return Struct( 

169 measurement=Measurement( 

170 metricName, np.mean(np.abs(result["corr"])), extras=extras 

171 ) 

172 )