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'], ignore_crowding=False, **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 ignore_crowding : bool (False) 

123 Ignore the cowding limit. 

124 

125 Returns 

126 ------- 

127 float 

128 The number of stars above the error limit 

129 """ 

130 

131 cols = [seeingCol, m5Col] 

132 units = 'N stars' 

133 self.crowding_error = crowding_error 

134 self.m5Col = m5Col 

135 self.filtername = filtername 

136 self.seeingCol = seeingCol 

137 self.ignore_crowding = ignore_crowding 

138 if metricName is None: 

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

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

141 

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

143 

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

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

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

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

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

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

150 crowdError = _compCrowdError(magVector, lumFunc, 

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

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

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

154 

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

156 crowdMag = max(magVector) 

157 else: 

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

159 

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

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

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

163 

164 # Use the shallower depth, crowding or coadded 

165 if self.ignore_crowding: 

166 min_mag = mag_limit 

167 else: 

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

169 

170 # Interpolate to the number of stars 

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

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

173 

174 return result 

175 

176 

177class CrowdingMagUncertMetric(BaseMetric): 

178 """ 

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

180 """ 

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

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

183 """ 

184 Parameters 

185 ---------- 

186 rmag : float 

187 The magnitude of the star to consider. 

188 

189 Returns 

190 ------- 

191 float 

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

193 """ 

194 

195 self.filtername = filtername 

196 self.seeingCol = seeingCol 

197 self.rmag = rmag 

198 if metricName is None: 

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

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

201 metricName=metricName, **kwargs) 

202 

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

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

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

206 # Magnitude uncertainty given crowding 

207 dmagCrowd = _compCrowdError(magVector, lumFunc, 

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

209 result = np.mean(dmagCrowd) 

210 return result