LSST Applications g0f08755f38+82efc23009,g12f32b3c4e+e7bdf1200e,g1653933729+a8ce1bb630,g1a0ca8cf93+50eff2b06f,g28da252d5a+52db39f6a5,g2bbee38e9b+37c5a29d61,g2bc492864f+37c5a29d61,g2cdde0e794+c05ff076ad,g3156d2b45e+41e33cbcdc,g347aa1857d+37c5a29d61,g35bb328faa+a8ce1bb630,g3a166c0a6a+37c5a29d61,g3e281a1b8c+fb992f5633,g414038480c+7f03dfc1b0,g41af890bb2+11b950c980,g5fbc88fb19+17cd334064,g6b1c1869cb+12dd639c9a,g781aacb6e4+a8ce1bb630,g80478fca09+72e9651da0,g82479be7b0+04c31367b4,g858d7b2824+82efc23009,g9125e01d80+a8ce1bb630,g9726552aa6+8047e3811d,ga5288a1d22+e532dc0a0b,gae0086650b+a8ce1bb630,gb58c049af0+d64f4d3760,gc28159a63d+37c5a29d61,gcf0d15dbbd+2acd6d4d48,gd7358e8bfb+778a810b6e,gda3e153d99+82efc23009,gda6a2b7d83+2acd6d4d48,gdaeeff99f8+1711a396fd,ge2409df99d+6b12de1076,ge79ae78c31+37c5a29d61,gf0baf85859+d0a5978c5a,gf3967379c6+4954f8c433,gfb92a5be7c+82efc23009,gfec2e1e490+2aaed99252,w.2024.46
LSST Data Management Base Package
Loading...
Searching...
No Matches
normalizedCalibrationFlux.py
Go to the documentation of this file.
1# This file is part of lsst.meas.algorithms.
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
22__all__ = ["NormalizedCalibrationFluxConfig", "NormalizedCalibrationFluxTask",
23 "NormalizedCalibrationFluxError"]
24
25import numpy as np
26
27from lsst.afw.image import ApCorrMap
28from lsst.afw.math import ChebyshevBoundedField
29import lsst.pex.config
30import lsst.pipe.base
31from .measureApCorr import MeasureApCorrTask, MeasureApCorrError
32from .sourceSelector import sourceSelectorRegistry
33
34
35class NormalizedCalibrationFluxError(lsst.pipe.base.AlgorithmError):
36 """Raised if Aperture Correction fails in a non-recoverable way.
37
38 Parameters
39 ----------
40 n_initial_sources : `int`
41 Number of sources selected by the fallback source selector.
42 n_calib_flux_flag : `int`
43 Number of selected sources with raw calibration flux flag unset.
44 n_ref_flux_flag : `int`
45 Number of selected sources with reference flux flag unset.
46 """
47 def __init__(self, *, n_initial_sources, n_calib_flux_flag, n_ref_flux_flag):
48 msg = "There are no valid stars to compute normalized calibration fluxes."
49 msg += (f" Of {n_initial_sources} initially selected sources, {n_calib_flux_flag} have good raw"
50 f" calibration fluxes and {n_ref_flux_flag} have good reference fluxes.")
51 super().__init__(msg)
52 self.n_initial_sources = n_initial_sources
53 self.n_calib_flux_flag = n_calib_flux_flag
54 self.n_ref_flux_flag = n_ref_flux_flag
55
56 @property
57 def metadata(self):
58 metadata = {"n_init_sources": self.n_initial_sources,
59 "n_calib_flux_flag": self.n_calib_flux_flag,
60 "n_ref_flux_flag": self.n_ref_flux_flag}
61 return metadata
62
63
65 """Configuration parameters for NormalizedCalibrationFluxTask.
66 """
68 target=MeasureApCorrTask,
69 doc="Subtask to measure aperture corrections.",
70 )
71 raw_calibflux_name = lsst.pex.config.Field(
72 doc="Name of raw calibration flux to normalize.",
73 dtype=str,
74 default="base_CompensatedTophatFlux_12",
75 )
76 normalized_calibflux_name = lsst.pex.config.Field(
77 doc="Name of normalized calibration flux.",
78 dtype=str,
79 default="base_NormalizedCompensatedTophatFlux",
80 )
81 do_set_calib_slot = lsst.pex.config.Field(
82 doc="Set the calib flux slot to the normalized flux?",
83 dtype=bool,
84 default=True,
85 )
86 do_measure_ap_corr = lsst.pex.config.Field(
87 doc="Measure the aperture correction? (Otherwise, just apply.)",
88 dtype=bool,
89 default=True,
90 )
91 fallback_source_selector = sourceSelectorRegistry.makeField(
92 doc="Selector that is used as a fallback if the full aperture correction "
93 "fails.",
94 default="science",
95 )
96
97 def setDefaults(self):
98 super().setDefaults()
99
100 self.measure_ap_corr.refFluxName = "base_CircularApertureFlux_12_0"
101
102 # This task is meant to be used early when we focus on PSF stars.
103 selector = self.measure_ap_corr.sourceSelector["science"]
104 selector.doUnresolved = False
105 selector.flags.good = ["calib_psf_used"]
106 selector.flags.bad = []
107 selector.signalToNoise.fluxField = self.raw_calibflux_name + "_instFlux"
108 selector.signalToNoise.errField = self.raw_calibflux_name + "_instFluxErr"
109 # Do median for this.
110 self.measure_ap_corr.fitConfig.orderX = 0
111 self.measure_ap_corr.fitConfig.orderY = 0
112
113 fallback_selector = self.fallback_source_selector["science"]
114 fallback_selector.doFluxLimit = False
115 fallback_selector.doFlags = True
116 fallback_selector.doUnresolved = False
117 fallback_selector.doSignalToNoise = False
118 fallback_selector.doIsolated = False
119 fallback_selector.flags.good = ["calib_psf_used"]
120 fallback_selector.flags.bad = []
121
122
123class NormalizedCalibrationFluxTask(lsst.pipe.base.Task):
124 """Task to measure the normalized calibration flux.
125
126 Parameters
127 ----------
128 schema : `lsst.afw.table.Schema`
129 Schema for the input table; will be modified in place.
130 **kwargs : `dict`
131 Additional kwargs to pass to lsst.pipe.base.Task.__init__()
132
133 Raises
134 ------
135 NormalizedCalibrationFluxError
136 Raised if there are not enough sources to calculate normalization.
137 """
138 ConfigClass = NormalizedCalibrationFluxConfig
139 _DefaultName = "normalizedCalibrationFlux"
140
141 def __init__(self, schema, **kwargs):
142 lsst.pipe.base.Task.__init__(self, **kwargs)
143
144 if self.config.do_measure_ap_corr:
145 self.makeSubtask(
146 "measure_ap_corr",
147 schema=schema,
148 namesToCorrect=[self.config.raw_calibflux_name],
149 )
150
151 name = self.config.normalized_calibflux_name
152 self.flux_name = name + "_instFlux"
153 if self.flux_name not in schema:
154 schema.addField(
155 self.flux_name,
156 type=float,
157 doc=f"Normalized calibration flux from {self.config.raw_calibflux_name}.",
158 )
159 self.err_name = name + "_instFluxErr"
160 if self.err_name not in schema:
161 schema.addField(
162 self.err_name,
163 type=float,
164 doc=f"Normalized calibration flux error from {self.config.raw_calibflux_name}.",
165 )
166 self.flag_name = name + "_flag"
167 if self.flag_name not in schema:
168 schema.addField(
169 self.flag_name,
170 type="Flag",
171 doc=f"Normalized calibration flux failure flag from {self.config.raw_calibflux_name}.",
172 )
173
174 if self.config.do_set_calib_slot:
175 schema.getAliasMap().set("slot_CalibFlux", name)
176
177 self.makeSubtask("fallback_source_selector")
178
179 self.schema = schema
180
181 def run(self, *, exposure, catalog):
182 """Measure the Normalized calibration flux.
183
184 Parameters
185 ----------
186 exposure : `lsst.afw.image.Exposure`
187 Exposure the normalized calibration flux is measured on.
188 catalog : `lsst.afw.table.SourceCatalog`
189 SourceCatalog containing measurements to be used to compute
190 normalized calibration fluxes. The catalog is modified in-place.
191
192 Returns
193 -------
194 Struct : `lsst.pipe.base.Struct`
195 Contains the following:
196
197 ``ap_corr_map``
198 aperture correction map (`lsst.afw.image.ApCorrMap`)
199 that contains two entries for the raw flux field:
200 - flux field (e.g. config.{raw_calibflux_name}_instFlux): 2d model
201 - flux sigma field (e.g. config.{raw_calibflux_name}_instFluxErr): 0 field
202 """
203 self.log.info("Measuring normalized calibration flux from %s", self.config.raw_calibflux_name)
204
205 raw_name = self.config.raw_calibflux_name
206 raw_flux_name = raw_name + "_instFlux"
207 raw_fluxerr_name = raw_name + "_instFluxErr"
208 norm_name = self.config.normalized_calibflux_name
209
210 if self.config.do_measure_ap_corr:
211 ap_corr_field, ap_corr_err_field = self._measure_aperture_correction(exposure, catalog)
212 else:
213 use_identity = False
214 ap_corr_map = exposure.info.getApCorrMap()
215 if ap_corr_map is None:
216 self.log.warning(
217 "Exposure does not have a valid normalization map; using identity normalization.",
218 )
219 use_identity = True
220 else:
221 ap_corr_field = ap_corr_map.get(raw_flux_name)
222 ap_corr_err_field = ap_corr_map.get(raw_fluxerr_name)
223 if not ap_corr_field or not ap_corr_err_field:
224 self.log.warning(
225 "Exposure aperture correction map is missing %s/%s for normalization; "
226 "using identity normalization.",
227 raw_flux_name,
228 raw_fluxerr_name,
229 )
230 use_identity = True
231
232 if use_identity:
233 ap_corr_field = ChebyshevBoundedField(exposure.getBBox(), np.array([[1.0]]))
234 ap_corr_err_field = ChebyshevBoundedField(exposure.getBBox(), np.array([[0.0]]))
235
236 corrections = ap_corr_field.evaluate(
237 catalog["slot_Centroid_x"],
238 catalog["slot_Centroid_y"],
239 )
240
241 input_flux_name = raw_flux_name
242 input_fluxerr_name = raw_fluxerr_name
243 input_flag_name = raw_name + "_flag"
244 output_flux_name = norm_name + "_instFlux"
245 output_fluxerr_name = norm_name + "_instFluxErr"
246 output_flag_name = norm_name + "_flag"
247
248 if catalog.isContiguous():
249 catalog[output_flux_name] = catalog[input_flux_name] * corrections
250 catalog[output_fluxerr_name] = catalog[input_fluxerr_name] * corrections
251
252 output_flag = catalog[input_flag_name].copy()
253 output_flag[corrections <= 0.0] = True
254 catalog[output_flag_name] = output_flag
255 else:
256 # If the catalog is not contiguous we must go row-by-row.
257 for i, row in enumerate(catalog):
258 row[output_flux_name] = row[input_flux_name] * corrections[i]
259 row[output_fluxerr_name] = row[input_fluxerr_name] * corrections[i]
260
261 if row[input_flag_name] or corrections[i] <= 0.0:
262 row[output_flag_name] = True
263
264 ap_corr_map = ApCorrMap()
265 ap_corr_map[raw_flux_name] = ap_corr_field
266 ap_corr_map[raw_fluxerr_name] = ap_corr_err_field
267
268 return lsst.pipe.base.Struct(
269 ap_corr_map=ap_corr_map,
270 )
271
272 def _measure_aperture_correction(self, exposure, catalog):
273 """Internal method to do the aperture correction measurement.
274
275 This measures the aperture correction with the regular task,
276 and if that fails does a fallback median estimate.
277
278 Parameters
279 ----------
280 exposure : `lsst.afw.image.Exposure`
281 Exposure the normalized calibration flux is measured on.
282 This is only used for the bounding box.
283 catalog : `lsst.afw.table.SourceCatalog`
284 SourceCatalog containing measurements to be used to compute
285 normalized calibration flux.
286
287 Returns
288 -------
289 ap_corr_field : `lsst.afw.math.ChebyshevBoundedField`
290 Aperture correction field to normalize the calibration flux.
291 ap_corr_err_field : `lsst.afw.math.ChebyshevBoundedField`
292 Aperture correction to adjust the calibration flux error.
293 """
294 raw_name = self.config.raw_calibflux_name
295
296 try:
297 ap_corr_map = self.measure_ap_corr.run(
298 exposure=exposure,
299 catalog=catalog,
300 ).apCorrMap
301
302 ap_corr_field = ap_corr_map.get(raw_name + "_instFlux")
303 except MeasureApCorrError as e:
304 self.log.warning("Failed to measure full aperture correction for %s with the following error %s",
305 raw_name, e)
306
307 initSel = self.fallback_source_selector.run(catalog, exposure=exposure).selected
308 sel = (initSel & ~catalog[self.config.raw_calibflux_name + "_flag"]
309 & ~catalog[self.config.measure_ap_corr.refFluxName + "_flag"])
310
311 if (n_sel := sel.sum()) == 0:
312 # This is a fatal error.
314 n_initial_sources=initSel.sum(),
315 n_calib_flux_flag=(initSel & ~catalog[self.config.raw_calibflux_name + "_flag"]).sum(),
316 n_ref_flux_flag=(initSel
317 & ~catalog[self.config.measure_ap_corr.refFluxName + "_flag"]).sum()
318 )
319 self.log.info("Measuring normalized flux correction with %d stars from fallback selector.",
320 n_sel)
321
322 ratio = np.median(
323 catalog[self.config.measure_ap_corr.refFluxName + "_instFlux"][sel]
324 / catalog[self.config.raw_calibflux_name + "_instFlux"][sel]
325 )
326
327 ap_corr_field = ChebyshevBoundedField(
328 exposure.getBBox(),
329 np.array([[ratio]]),
330 )
331
332 if catalog.isContiguous():
333 catalog["apcorr_" + raw_name + "_used"] = sel
334 else:
335 for i, row in enumerate(catalog):
336 row["apcorr_" + raw_name + "_used"] = sel[i]
337
338 # We are always setting the error field to 0, because we do not
339 # have a good model for aperture correction uncertainties.
340 ap_corr_err_field = ChebyshevBoundedField(
341 exposure.getBBox(),
342 np.array([[0.0]]),
343 )
344
345 return ap_corr_field, ap_corr_err_field
A thin wrapper around std::map to allow aperture corrections to be attached to Exposures.
Definition ApCorrMap.h:45
A BoundedField based on 2-d Chebyshev polynomials of the first kind.
__init__(self, *n_initial_sources, n_calib_flux_flag, n_ref_flux_flag)