Coverage for python/lsst/sims/maf/metrics/crowdingMetric.py : 16%

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
6# Modifying from Knut Olson's fork at:
7# https://github.com/knutago/sims_maf_contrib/blob/master/tutorials/CrowdingMetric.ipynb
9__all__ = ['CrowdingM5Metric', 'CrowdingMagUncertMetric', 'NstarsMetric']
12def _compCrowdError(magVector, lumFunc, seeing, singleMag=None):
13 """
14 Compute the photometric crowding error given the luminosity function and best seeing.
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.
29 Returns
30 -------
31 np.array
32 Magnitude uncertainties.
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
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.
67 Returns
68 -------
69 float
70 The magnitude of a star which has a photometric error of `crowding_error`
71 """
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)
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]
93 if np.size(aboveCrowd) == 0:
94 result = max(magVector)
95 else:
96 crowdMag = magVector[max(aboveCrowd[0]-1, 0)]
97 result = crowdMag
99 return result
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.
125 Returns
126 -------
127 float
128 The number of stars above the error limit
129 """
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)
142 def run(self, dataSlice, slicePoint=None):
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]
155 if np.size(aboveCrowd) == 0:
156 crowdMag = max(magVector)
157 else:
158 crowdMag = magVector[max(aboveCrowd[0]-1, 0)]
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
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])
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
174 return result
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.
189 Returns
190 -------
191 float
192 The uncertainty in magnitudes caused by crowding for a star of rmag.
193 """
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)
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