Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1import numpy as np 

2from scipy.interpolate import interp1d 

3from lsst.sims.maf.metrics import BaseMetric 

4import healpy as hp 

5 

6# Modifying from Knut Olson's fork at: 

7# https://github.com/knutago/sims_maf_contrib/blob/master/tutorials/CrowdingMetric.ipynb 

8 

9__all__ = ['CrowdingM5Metric', 'CrowdingMagUncertMetric', 'NstarsMetric'] 

10 

11 

12def _compCrowdError(magVector, lumFunc, seeing, singleMag=None): 

13 """ 

14 Compute the photometric crowding error given the luminosity function and best seeing. 

15 

16 Parameters 

17 ---------- 

18 magVector : np.array 

19 Stellar magnitudes. 

20 lumFunc : np.array 

21 Stellar luminosity function. 

22 seeing : float 

23 The best seeing conditions. Assuming forced-photometry can use the best seeing conditions 

24 to help with confusion errors. 

25 singleMag : float (None) 

26 If singleMag is None, the crowding error is calculated for each mag in magVector. If 

27 singleMag is a float, the crowding error is interpolated to that single value. 

28 

29 Returns 

30 ------- 

31 np.array 

32 Magnitude uncertainties. 

33 

34 Equation from Olsen, Blum, & Rigaut 2003, AJ, 126, 452 

35 """ 

36 lumAreaArcsec = 3600.0 ** 2 

37 lumVector = 10 ** (-0.4 * magVector) 

38 coeff = np.sqrt(np.pi / lumAreaArcsec) * seeing / 2. 

39 myInt = (np.add.accumulate((lumVector ** 2 * lumFunc)[::-1]))[::-1] 

40 temp = np.sqrt(myInt) / lumVector 

41 if singleMag is not None: 

42 interp = interp1d(magVector, temp) 

43 temp = interp(singleMag) 

44 crowdError = coeff * temp 

45 return crowdError 

46 

47 

48class CrowdingM5Metric(BaseMetric): 

49 """Return the magnitude at which the photometric error exceeds crowding_error threshold. 

50 """ 

51 def __init__(self, crowding_error=0.1, filtername='r', seeingCol='seeingFwhmGeom', 

52 metricName=None, maps=['StellarDensityMap'], **kwargs): 

53 """ 

54 Parameters 

55 ---------- 

56 crowding_error : float, opt 

57 The magnitude uncertainty from crowding in magnitudes. Default 0.1 mags. 

58 filtername: str, opt 

59 The bandpass in which to calculate the crowding limit. Default r. 

60 seeingCol : str, opt 

61 The name of the seeing column. 

62 m5Col : str, opt 

63 The name of the m5 depth column. 

64 maps : list of str, opt 

65 Names of maps required for the metric. 

66 

67 Returns 

68 ------- 

69 float 

70 The magnitude of a star which has a photometric error of `crowding_error` 

71 """ 

72 

73 cols = [seeingCol] 

74 units = 'mag' 

75 self.crowding_error = crowding_error 

76 self.filtername = filtername 

77 self.seeingCol = seeingCol 

78 if metricName is None: 

79 metricName = 'Crowding to Precision %.2f' % (crowding_error) 

80 super().__init__(col=cols, maps=maps, units=units, metricName=metricName, **kwargs) 

81 

82 def run(self, dataSlice, slicePoint=None): 

83 # Set magVector to the same length as starLumFunc (lower edge of mag bins) 

84 magVector = slicePoint[f'starMapBins_{self.filtername}'][1:] 

85 # Pull up density of stars at this point in the sky 

86 lumFunc = slicePoint[f'starLumFunc_{self.filtername}'] 

87 # Calculate the crowding error using the best seeing value (in any filter?) 

88 crowdError = _compCrowdError(magVector, lumFunc, 

89 seeing=min(dataSlice[self.seeingCol])) 

90 # Locate at which point crowding error is greater than user-defined limit 

91 aboveCrowd = np.where(crowdError >= self.crowding_error)[0] 

92 

93 if np.size(aboveCrowd) == 0: 

94 result = max(magVector) 

95 else: 

96 crowdMag = magVector[max(aboveCrowd[0]-1, 0)] 

97 result = crowdMag 

98 

99 return result 

100 

101 

102class NstarsMetric(BaseMetric): 

103 """Return the number of stars visible above some uncertainty limit, 

104 taking image depth and crowding into account. 

105 """ 

106 def __init__(self, crowding_error=0.1, filtername='r', seeingCol='seeingFwhmGeom', 

107 m5Col='fiveSigmaDepth', 

108 metricName=None, maps=['StellarDensityMap'], **kwargs): 

109 """ 

110 Parameters 

111 ---------- 

112 crowding_error : float, opt 

113 The magnitude uncertainty from crowding in magnitudes. Default 0.1 mags. 

114 filtername: str, opt 

115 The bandpass in which to calculate the crowding limit. Default r. 

116 seeingCol : str, opt 

117 The name of the seeing column. 

118 m5Col : str, opt 

119 The name of the m5 depth column. 

120 maps : list of str, opt 

121 Names of maps required for the metric. 

122 

123 Returns 

124 ------- 

125 float 

126 The number of stars above the error limit 

127 """ 

128 

129 cols = [seeingCol, m5Col] 

130 units = 'N stars' 

131 self.crowding_error = crowding_error 

132 self.m5Col = m5Col 

133 self.filtername = filtername 

134 self.seeingCol = seeingCol 

135 if metricName is None: 

136 metricName = 'N stars to Precision %.2f' % (crowding_error) 

137 super().__init__(col=cols, maps=maps, units=units, metricName=metricName, **kwargs) 

138 

139 def run(self, dataSlice, slicePoint=None): 

140 

141 pix_area = hp.nside2pixarea(slicePoint['nside'], degrees=True) 

142 # Set magVector to the same length as starLumFunc (lower edge of mag bins) 

143 magVector = slicePoint[f'starMapBins_{self.filtername}'][1:] 

144 # Pull up density of stars at this point in the sky 

145 lumFunc = slicePoint[f'starLumFunc_{self.filtername}'] 

146 # Calculate the crowding error using the best seeing value (in any filter?) 

147 crowdError = _compCrowdError(magVector, lumFunc, 

148 seeing=min(dataSlice[self.seeingCol])) 

149 # Locate at which point crowding error is greater than user-defined limit 

150 aboveCrowd = np.where(crowdError >= self.crowding_error)[0] 

151 

152 if np.size(aboveCrowd) == 0: 

153 crowdMag = max(magVector) 

154 else: 

155 crowdMag = magVector[max(aboveCrowd[0]-1, 0)] 

156 

157 # Compute the coadded depth, and the mag where that depth hits the error specified 

158 coadded_depth = 1.25 * np.log10(np.sum(10.**(.8*dataSlice[self.m5Col]))) 

159 mag_limit = 2.5*np.log10(self.crowding_error/(1.09*5))+coadded_depth 

160 

161 # Use the shallower depth, crowding or coadded 

162 min_mag = np.min([crowdMag, mag_limit]) 

163 

164 # Interpolate to the number of stars 

165 result = np.interp(min_mag, slicePoint[f'starMapBins_{self.filtername}'][1:], 

166 slicePoint[f'starLumFunc_{self.filtername}']) * pix_area 

167 

168 return result 

169 

170 

171class CrowdingMagUncertMetric(BaseMetric): 

172 """ 

173 Given a stellar magnitude, calculate the mean uncertainty on the magnitude from crowding. 

174 """ 

175 def __init__(self, rmag=20., seeingCol='seeingFwhmGeom', units='mag', 

176 metricName=None, filtername='r', maps=['StellarDensityMap'], **kwargs): 

177 """ 

178 Parameters 

179 ---------- 

180 rmag : float 

181 The magnitude of the star to consider. 

182 

183 Returns 

184 ------- 

185 float 

186 The uncertainty in magnitudes caused by crowding for a star of rmag. 

187 """ 

188 

189 self.filtername = filtername 

190 self.seeingCol = seeingCol 

191 self.rmag = rmag 

192 if metricName is None: 

193 metricName = 'CrowdingError at %.2f' % (rmag) 

194 super().__init__(col=[seeingCol], maps=maps, units=units, 

195 metricName=metricName, **kwargs) 

196 

197 def run(self, dataSlice, slicePoint=None): 

198 magVector = slicePoint[f'starMapBins_{self.filtername}'][1:] 

199 lumFunc = slicePoint[f'starLumFunc_{self.filtername}'] 

200 # Magnitude uncertainty given crowding 

201 dmagCrowd = _compCrowdError(magVector, lumFunc, 

202 dataSlice[self.seeingCol], singleMag=self.rmag) 

203 result = np.mean(dmagCrowd) 

204 return result