Coverage for python/lsst/validate/drp/photerrmodel.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
1# LSST Data Management System
2# Copyright 2016 AURA/LSST.
3#
4# This product includes software developed by the
5# LSST Project (http://www.lsst.org/).
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the LSST License Statement and
18# the GNU General Public License along with this program. If not,
19# see <https://www.lsstcorp.org/LegalNotices/>.
20"""Analytic single-visit photometric error model.
21"""
23__all__ = ['photErrModel', 'fitPhotErrModel', 'build_photometric_error_model']
25import astropy.units as u
26import numpy as np
27from scipy.optimize import curve_fit
29from lsst.verify import Blob, Datum
32def photErrModel(mag, sigmaSys, gamma, m5):
33 r"""Model of photometric error for a single visit.
35 The model is described in the LSST Overview paper:
36 http://arxiv.org/abs/0805.2366v4.
38 Photometric error of a single visit (Eq. 4):
40 .. math:
42 \sigma_1^2 = \sigma_\mathrm{sys}^2 + \sigma_\mathrm{rand}^2
44 Where random uncertainty is (Eq. 5):
46 .. math::
48 \sigma_\mathrm{rand}^2 = (0.04 - \gamma) * x + \gamma * x^2~[\mathrm{mag}^2]
50 and $x = 10^(0.4*(m-m_5))$.
52 Parameters
53 ----------
54 mag : `astropy.unit.Quantity`
55 Source magnitude.
56 sigmaSq : `astropy.unit.Quantity`
57 Systematic photometric error (magnitude).
58 gamma : `astropy.unit.Quantity`
59 Proxy for sky brightness and readout noise (dimensionless).
60 m5 : `astropy.unit.Quantity`
61 5-sigma depth (magnitude).
63 Returns
64 -------
65 sigma : `astropy.units.Quantity`
66 Photometric error for a single visit.
67 """
68 x = 10**(0.4*(mag - m5))
69 sigmaRandSq = (0.04 - gamma) * x + gamma * x**2
70 sigmaSq = sigmaSys**2 + sigmaRandSq
71 return np.sqrt(sigmaSq)
74def fitPhotErrModel(mag, mag_err):
75 """Fit photometric error model from the LSST Overview paper:
77 http://arxiv.org/abs/0805.2366v4
79 The fit is performed with `scipy.optimize.curvefit`.
81 Parameters
82 ----------
83 mag : `astropy.units.Quantity`
84 Magnitude.
85 mag_err : `astropy.units.Quantity`
86 Magnitude uncertainty or variation.
88 Returns
89 -------
90 params : `dict`
91 Fitted model. Fields are:
93 - `sigmaSys`: systematic error (magnitude, `astropy.units.Quantity`).
94 - `gamma`: Proxy for sky brightness and readout noise (dimensionless,
95 `astropy.units.Quantity`).
96 - `m5`: 5-sigma limiting depth (magnitude, `astropy.units.Quantity`).
98 See also
99 --------
100 photErrModel
101 """
102 # if not isinstance(mag, u.Quantity):
103 # mag = mag * u.mag
104 # if not isinstance(mag_err, u.Quantity):
105 # mag_err = mag_err * u.mag
107 # p0 = [0.01 * u.mag, # sigmaSys
108 # 0.039 * u.Unit(''), # gamma
109 # 24.35 * u.mag] # m5
111 # fit_params, fit_param_covariance = curve_fit(
112 # photErrModel, mag, mag_err, p0=p0)
114 # params = {
115 # 'sigmaSys': fit_params[0],
116 # 'gamma': fit_params[1],
117 # 'm5': fit_params[2],
118 # }
119 if isinstance(mag, u.Quantity):
120 mag = mag.to(u.mag).value
121 if isinstance(mag_err, u.Quantity):
122 mag_err = mag_err.to(u.mag).value
124 p0 = [0.01, # sigmaSys (mag)
125 0.039, # gamma ('')
126 24.35] # m5 (mag)
128 try:
129 fit_params, fit_param_covariance = curve_fit(
130 photErrModel, mag, mag_err, p0=p0)
131 sigmaSys, gamma, m5 = fit_params
132 except (RuntimeError, ValueError) as e:
133 print("fitPhotErrorModel fitting failed with")
134 print(e)
135 print("sigmaSys, gamma, m5 are being set to NaN.")
136 sigmaSys, gamma, m5 = np.nan, np.nan, np.nan
138 params = {
139 'sigmaSys': sigmaSys * u.mag,
140 'gamma': gamma * u.Unit(''),
141 'm5': m5 * u.mag,
142 }
143 return params
146def build_photometric_error_model(matchedMultiVisitDataset, selection, medianRef=100, matchRef=500):
147 r"""Returns a serializable analytic photometry error model for a single visit.
149 This model is originally presented in http://arxiv.org/abs/0805.2366v4
150 (Eq 4, 5):
152 .. math::
154 \sigma_1^2 &= \sigma_\mathrm{sys}^2 + \sigma_\mathrm{rand}^2 \\
155 x &= 10^{0.4(m-m_5)} \\
156 \sigma_\mathrm{rand}^2 &= (0.04 - \gamma) x + \gamma x^2~[\mathrm{mag}^2]
158 Parameters
159 ----------
160 matchedMultiVisitDataset : `lsst.valididate.drp.matchreduce.MatchedMultiVisitDataset`
161 A dataset containing matched statistics for stars across multiple visits.
162 selection : `np.array` of `bool`
163 The selection of sources to use to build the model.
164 medianRef : `float` or `astropy.unit.Quantity`, optional
165 Median reference astrometric scatter (millimagnitudes by default).
166 matchRef : `int` or `astropy.unit.Quantity`, optional
167 Should match at least matchRef stars.
169 Returns
170 ----------
171 blob : `lsst.verify.Blob`
172 Blob with datums:
174 - ``sigmaSys``: Systematic error floor.
175 - ``gamma``: Proxy for sky brightness and read noise.
176 - ``m5``: 5-sigma photometric depth (magnitudes).
177 - ``photRms``: RMS photometric scatter for 'good' stars (millimagnitudes).
179 Notes
180 -----
181 The scatter and match defaults are appropriate to SDSS are stored here.
182 For SDSS, stars with mag < 19.5 should be completely well measured.
183 This limit is a band-dependent statement most appropriate to r.
184 """
185 blob = Blob('PhotometricErrorModel')
186 for field in ('brightSnrMin', 'brightSnrMax'):
187 blob[field] = matchedMultiVisitDataset[field]
189 # FIXME add a description field to blobs?
190 # _doc['doc'] \
191 # = "Photometric uncertainty model from " \
192 # "http://arxiv.org/abs/0805.2366v4 (Eq 4, 5): " \
193 # "sigma_1^2 = sigma_sys^2 + sigma_rand^2, " \
194 # "sigma_rand^2 = (0.04 - gamma) * x + gamma * x^2 [mag^2] " \
195 # "where x = 10**(0.4*(m-m_5))"
197 if not isinstance(medianRef, u.Quantity):
198 medianRef = medianRef * u.mmag
199 _compute(blob,
200 selection,
201 matchedMultiVisitDataset['mag'].quantity,
202 matchedMultiVisitDataset['magerr'].quantity,
203 matchedMultiVisitDataset['magrms'].quantity,
204 medianRef,
205 matchRef)
206 return blob
209def _compute(blob, bright, mag, magErr, magRms, medianRef, matchRef):
210 nMatch = np.sum(bright)
211 blob['photScatter'] = Datum(quantity=np.median(magRms[bright]),
212 label='RMS',
213 description='RMS photometric scatter for good stars')
214 print('Photometric scatter (median) - SNR > {0:.1f} : {1:.1f}'.format(
215 blob['brightSnrMin'].quantity, blob['photScatter'].quantity.to(u.mmag)))
217 fit_params = fitPhotErrModel(mag[bright], magErr[bright])
218 blob['sigmaSys'] = Datum(quantity=fit_params['sigmaSys'],
219 label='sigma(sys)',
220 description='Systematic error floor')
221 blob['gamma'] = Datum(quantity=fit_params['gamma'],
222 label='gamma',
223 description='Proxy for sky brightness and read noise')
224 blob['m5'] = Datum(quantity=fit_params['m5'],
225 label='m5',
226 description='5-sigma depth')
228 if blob['photScatter'].quantity > medianRef:
229 msg = 'Median photometric scatter {0:.3f} is larger than ' \
230 'reference : {1:.3f}'
231 print(msg.format(blob['photScatter'].quantity.value, medianRef))
232 if nMatch < matchRef:
233 msg = 'Number of matched sources {0:d} is too small ' \
234 '(should be > {0:d})'
235 print(msg.format(nMatch, matchRef))