LSST Applications g070148d5b3+33e5256705,g0d53e28543+25c8b88941,g0da5cf3356+2dd1178308,g1081da9e2a+62d12e78cb,g17e5ecfddb+7e422d6136,g1c76d35bf8+ede3a706f7,g295839609d+225697d880,g2e2c1a68ba+cc1f6f037e,g2ffcdf413f+853cd4dcde,g38293774b4+62d12e78cb,g3b44f30a73+d953f1ac34,g48ccf36440+885b902d19,g4b2f1765b6+7dedbde6d2,g5320a0a9f6+0c5d6105b6,g56b687f8c9+ede3a706f7,g5c4744a4d9+ef6ac23297,g5ffd174ac0+0c5d6105b6,g6075d09f38+66af417445,g667d525e37+2ced63db88,g670421136f+2ced63db88,g71f27ac40c+2ced63db88,g774830318a+463cbe8d1f,g7876bc68e5+1d137996f1,g7985c39107+62d12e78cb,g7fdac2220c+0fd8241c05,g96f01af41f+368e6903a7,g9ca82378b8+2ced63db88,g9d27549199+ef6ac23297,gabe93b2c52+e3573e3735,gb065e2a02a+3dfbe639da,gbc3249ced9+0c5d6105b6,gbec6a3398f+0c5d6105b6,gc9534b9d65+35b9f25267,gd01420fc67+0c5d6105b6,geee7ff78d7+a14128c129,gf63283c776+ede3a706f7,gfed783d017+0c5d6105b6,w.2022.47
LSST Data Management Base Package
Loading...
Searching...
No Matches
photoCal.py
Go to the documentation of this file.
1# This file is part of pipe_tasks.
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__ = ["PhotoCalTask", "PhotoCalConfig"]
23
24import math
25import sys
26
27import numpy as np
28import astropy.units as u
29
30import lsst.pex.config as pexConf
31import lsst.pipe.base as pipeBase
32from lsst.afw.image import abMagErrFromFluxErr, makePhotoCalibFromCalibZeroPoint
33import lsst.afw.table as afwTable
34from lsst.meas.astrom import DirectMatchTask, DirectMatchConfigWithoutLoader
35import lsst.afw.display as afwDisplay
36from lsst.meas.algorithms import getRefFluxField, ReserveSourcesTask
37from lsst.utils.timer import timeMethod
38from .colorterms import ColortermLibrary
39
40
41class PhotoCalConfig(pexConf.Config):
42 """Config for PhotoCal."""
43
44 match = pexConf.ConfigField("Match to reference catalog",
45 DirectMatchConfigWithoutLoader)
46 reserve = pexConf.ConfigurableField(target=ReserveSourcesTask, doc="Reserve sources from fitting")
47 fluxField = pexConf.Field(
48 dtype=str,
49 default="slot_CalibFlux_instFlux",
50 doc=("Name of the source instFlux field to use.\nThe associated flag field "
51 "('<name>_flags') will be implicitly included in badFlags."),
52 )
53 applyColorTerms = pexConf.Field(
54 dtype=bool,
55 default=False,
56 doc=("Apply photometric color terms to reference stars?\n"
57 "`True`: attempt to apply color terms; fail if color term data is "
58 "not available for the specified reference catalog and filter.\n"
59 "`False`: do not apply color terms."),
60 optional=True,
61 )
62 sigmaMax = pexConf.Field(
63 dtype=float,
64 default=0.25,
65 doc="maximum sigma to use when clipping",
66 optional=True,
67 )
68 nSigma = pexConf.Field(
69 dtype=float,
70 default=3.0,
71 doc="clip at nSigma",
72 )
73 useMedian = pexConf.Field(
74 dtype=bool,
75 default=True,
76 doc="use median instead of mean to compute zeropoint",
77 )
78 nIter = pexConf.Field(
79 dtype=int,
80 default=20,
81 doc="number of iterations",
82 )
83 colorterms = pexConf.ConfigField(
84 dtype=ColortermLibrary,
85 doc="Library of photometric reference catalog name: color term dict (see also applyColorTerms).",
86 )
87 photoCatName = pexConf.Field(
88 dtype=str,
89 optional=True,
90 doc=("Name of photometric reference catalog; used to select a color term dict in colorterms.\n"
91 "See also applyColorTerms."),
92 )
93 magErrFloor = pexConf.RangeField(
94 dtype=float,
95 default=0.0,
96 doc="Additional magnitude uncertainty to be added in quadrature with measurement errors.",
97 min=0.0,
98 )
99
100 def validate(self):
101 pexConf.Config.validate(self)
102 if self.applyColorTerms and self.photoCatName is None:
103 raise RuntimeError("applyColorTerms=True requires photoCatName is non-None")
104 if self.applyColorTerms and len(self.colorterms.data) == 0:
105 raise RuntimeError("applyColorTerms=True requires colorterms be provided")
106
107 def setDefaults(self):
108 pexConf.Config.setDefaults(self)
109 self.match.sourceSelection.doFlags = True
110 self.match.sourceSelection.flags.bad = [
111 "base_PixelFlags_flag_edge",
112 "base_PixelFlags_flag_interpolated",
113 "base_PixelFlags_flag_saturated",
114 ]
115 self.match.sourceSelection.doUnresolved = True
116
117
118class PhotoCalTask(pipeBase.Task):
119 """Calculate an Exposure's zero-point given a set of flux measurements
120 of stars matched to an input catalogue.
121
122 Parameters
123 ----------
125 An instance of LoadReferenceObjectsTasks that supplies an external reference
126 catalog.
127 schema : `lsst.afw.table.Schema`, optional
128 The schema of the detection catalogs used as input to this task.
129 **kwds
130 Additional keyword arguments.
131
132 Notes
133 -----
134 The type of flux to use is specified by PhotoCalConfig.fluxField.
135
136 The algorithm clips outliers iteratively, with parameters set in the configuration.
137
138 This task can adds fields to the schema, so any code calling this task must ensure that
139 these columns are indeed present in the input match list; see `pipe_tasks_photocal_Example`.
140
141 Debugging:
142
143 The available `~lsst.base.lsstDebug` variables in PhotoCalTask are:
144
145 display :
146 If True enable other debug outputs.
147 displaySources :
148 If True, display the exposure on ds9's frame 1 and overlay the source catalogue.
149
150 red o :
151 Reserved objects.
152 green o :
153 Objects used in the photometric calibration.
154
155 scatterPlot :
156 Make a scatter plot of flux v. reference magnitude as a function of reference magnitude:
157
158 - good objects in blue
159 - rejected objects in red
160
161 (if scatterPlot is 2 or more, prompt to continue after each iteration)
162 """
163
164 ConfigClass = PhotoCalConfig
165 _DefaultName = "photoCal"
166
167 def __init__(self, refObjLoader, schema=None, **kwds):
168 pipeBase.Task.__init__(self, **kwds)
169 self.scatterPlot = None
170 self.fig = None
171 if schema is not None:
172 self.usedKey = schema.addField("calib_photometry_used", type="Flag",
173 doc="set if source was used in photometric calibration")
174 else:
175 self.usedKey = None
176 self.match = DirectMatchTask(config=self.config.match, refObjLoader=refObjLoader,
177 name="match", parentTask=self)
178 self.makeSubtask("reserve", columnName="calib_photometry", schema=schema,
179 doc="set if source was reserved from photometric calibration")
180
181 def getSourceKeys(self, schema):
182 """Return a struct containing the source catalog keys for fields used
183 by PhotoCalTask.
184
185 Parameters
186 ----------
187 schema : `lsst.afw.table.schema`
188 Schema of the catalog to get keys from.
189
190 Returns
191 -------
192 result : `lsst.pipe.base.Struct`
193 Results as a struct with attributes:
194
195 ``instFlux``
196 Instrument flux key.
197 ``instFluxErr``
198 Instrument flux error key.
199 """
200 instFlux = schema.find(self.config.fluxField).key
201 instFluxErr = schema.find(self.config.fluxField + "Err").key
202 return pipeBase.Struct(instFlux=instFlux, instFluxErr=instFluxErr)
203
204 @timeMethod
205 def extractMagArrays(self, matches, filterLabel, sourceKeys):
206 """Extract magnitude and magnitude error arrays from the given matches.
207
208 Parameters
209 ----------
211 Reference/source matches.
212 filterLabel : `str`
213 Label of filter being calibrated.
214 sourceKeys : `lsst.pipe.base.Struct`
215 Struct of source catalog keys, as returned by getSourceKeys().
216
217 Returns
218 -------
219 result : `lsst.pipe.base.Struct`
220 Results as a struct with attributes:
221
222 ``srcMag``
223 Source magnitude (`np.array`).
224 ``refMag``
225 Reference magnitude (`np.array`).
226 ``srcMagErr``
227 Source magnitude error (`np.array`).
228 ``refMagErr``
229 Reference magnitude error (`np.array`).
230 ``magErr``
231 An error in the magnitude; the error in ``srcMag`` - ``refMag``.
232 If nonzero, ``config.magErrFloor`` will be added to ``magErr`` only
233 (not ``srcMagErr`` or ``refMagErr``), as
234 ``magErr`` is what is later used to determine the zero point (`np.array`).
235 ``refFluxFieldList``
236 A list of field names of the reference catalog used for fluxes (1 or 2 strings) (`list`).
237 """
238 srcInstFluxArr = np.array([m.second.get(sourceKeys.instFlux) for m in matches])
239 srcInstFluxErrArr = np.array([m.second.get(sourceKeys.instFluxErr) for m in matches])
240 if not np.all(np.isfinite(srcInstFluxErrArr)):
241 # this is an unpleasant hack; see DM-2308 requesting a better solution
242 self.log.warning("Source catalog does not have flux uncertainties; using sqrt(flux).")
243 srcInstFluxErrArr = np.sqrt(srcInstFluxArr)
244
245 # convert source instFlux from DN to an estimate of nJy
246 referenceFlux = (0*u.ABmag).to_value(u.nJy)
247 srcInstFluxArr = srcInstFluxArr * referenceFlux
248 srcInstFluxErrArr = srcInstFluxErrArr * referenceFlux
249
250 if not matches:
251 raise RuntimeError("No reference stars are available")
252 refSchema = matches[0].first.schema
253
254 if self.config.applyColorTerms:
255 self.log.info("Applying color terms for filter=%r, config.photoCatName=%s",
256 filterLabel.physicalLabel, self.config.photoCatName)
257 colorterm = self.config.colorterms.getColorterm(filterLabel.physicalLabel,
258 self.config.photoCatName,
259 doRaise=True)
260 refCat = afwTable.SimpleCatalog(matches[0].first.schema)
261
262 # extract the matched refCat as a Catalog for the colorterm code
263 refCat.reserve(len(matches))
264 for x in matches:
265 record = refCat.addNew()
266 record.assign(x.first)
267
268 refMagArr, refMagErrArr = colorterm.getCorrectedMagnitudes(refCat)
269 fluxFieldList = [getRefFluxField(refSchema, filt) for filt in (colorterm.primary,
270 colorterm.secondary)]
271 else:
272 self.log.info("Not applying color terms.")
273 colorterm = None
274
275 fluxFieldList = [getRefFluxField(refSchema, filterLabel.bandLabel)]
276 fluxField = getRefFluxField(refSchema, filterLabel.bandLabel)
277 fluxKey = refSchema.find(fluxField).key
278 refFluxArr = np.array([m.first.get(fluxKey) for m in matches])
279
280 try:
281 fluxErrKey = refSchema.find(fluxField + "Err").key
282 refFluxErrArr = np.array([m.first.get(fluxErrKey) for m in matches])
283 except KeyError:
284 # Reference catalogue may not have flux uncertainties; HACK DM-2308
285 self.log.warning("Reference catalog does not have flux uncertainties for %s;"
286 " using sqrt(flux).", fluxField)
287 refFluxErrArr = np.sqrt(refFluxArr)
288
289 refMagArr = u.Quantity(refFluxArr, u.nJy).to_value(u.ABmag)
290 # HACK convert to Jy until we have a replacement for this (DM-16903)
291 refMagErrArr = abMagErrFromFluxErr(refFluxErrArr*1e-9, refFluxArr*1e-9)
292
293 # compute the source catalog magnitudes and errors
294 srcMagArr = u.Quantity(srcInstFluxArr, u.nJy).to_value(u.ABmag)
295 # Fitting with error bars in both axes is hard
296 # for now ignore reference flux error, but ticket DM-2308 is a request for a better solution
297 # HACK convert to Jy until we have a replacement for this (DM-16903)
298 magErrArr = abMagErrFromFluxErr(srcInstFluxErrArr*1e-9, srcInstFluxArr*1e-9)
299 if self.config.magErrFloor != 0.0:
300 magErrArr = (magErrArr**2 + self.config.magErrFloor**2)**0.5
301
302 srcMagErrArr = abMagErrFromFluxErr(srcInstFluxErrArr*1e-9, srcInstFluxArr*1e-9)
303
304 good = np.isfinite(srcMagArr) & np.isfinite(refMagArr)
305
306 return pipeBase.Struct(
307 srcMag=srcMagArr[good],
308 refMag=refMagArr[good],
309 magErr=magErrArr[good],
310 srcMagErr=srcMagErrArr[good],
311 refMagErr=refMagErrArr[good],
312 refFluxFieldList=fluxFieldList,
313 )
314
315 @timeMethod
316 def run(self, exposure, sourceCat, expId=0):
317 """Do photometric calibration - select matches to use and (possibly iteratively) compute
318 the zero point.
319
320 Parameters
321 ----------
322 exposure : `lsst.afw.image.Exposure`
323 Exposure upon which the sources in the matches were detected.
324 sourceCat : `lsst.afw.image.SourceCatalog`
325 A catalog of sources to use in the calibration
326 (i.e. a `list` of `lsst.afw.table.Match` with
327 first being of type `lsst.afw.table.SimpleRecord` and second type `lsst.afw.table.SourceRecord`
328 the reference object and matched object respectively).
329 Will not be modified except to set the outputField if requested.
330 expId : `int`, optional
331 Exposure ID.
332
333 Returns
334 -------
335 result : `lsst.pipe.base.Struct`
336 Results as a struct with attributes:
337
338 ``photoCalib``
339 Object containing the zero point (`lsst.afw.image.Calib`).
340 ``arrays``
341 Magnitude arrays returned be `PhotoCalTask.extractMagArrays`.
342 ``matches``
343 ReferenceMatchVector, as returned by `PhotoCalTask.selectMatches`.
344 ``zp``
345 Photometric zero point (mag, `float`).
346 ``sigma``
347 Standard deviation of fit of photometric zero point (mag, `float`).
348 ``ngood``
349 Number of sources used to fit photometric zero point (`int`).
350
351 Raises
352 ------
353 RuntimeError
354 Raised if any of the following occur:
355 - No matches to use for photocal.
356 - No matches are available (perhaps no sources/references were selected by the matcher).
357 - No reference stars are available.
358 - No matches are available from which to extract magnitudes.
359
360 Notes
361 -----
362 The exposure is only used to provide the name of the filter being calibrated (it may also be
363 used to generate debugging plots).
364
365 The reference objects:
366 - Must include a field ``photometric``; True for objects which should be considered as
367 photometric standards.
368 - Must include a field ``flux``; the flux used to impose a magnitude limit and also to calibrate
369 the data to (unless a color term is specified, in which case ColorTerm.primary is used;
370 See https://jira.lsstcorp.org/browse/DM-933).
371 - May include a field ``stargal``; if present, True means that the object is a star.
372 - May include a field ``var``; if present, True means that the object is variable.
373
374 The measured sources:
375 - Must include PhotoCalConfig.fluxField; the flux measurement to be used for calibration.
376 """
377 import lsstDebug
378
379 display = lsstDebug.Info(__name__).display
380 displaySources = display and lsstDebug.Info(__name__).displaySources
381 self.scatterPlot = display and lsstDebug.Info(__name__).scatterPlot
382
383 if self.scatterPlot:
384 from matplotlib import pyplot
385 try:
386 self.fig.clf()
387 except Exception:
388 self.fig = pyplot.figure()
389
390 filterLabel = exposure.getFilter()
391
392 # Match sources
393 matchResults = self.match.run(sourceCat, filterLabel.bandLabel)
394 matches = matchResults.matches
395
396 reserveResults = self.reserve.run([mm.second for mm in matches], expId=expId)
397 if displaySources:
398 self.displaySources(exposure, matches, reserveResults.reserved)
399 if reserveResults.reserved.sum() > 0:
400 matches = [mm for mm, use in zip(matches, reserveResults.use) if use]
401 if len(matches) == 0:
402 raise RuntimeError("No matches to use for photocal")
403 if self.usedKey is not None:
404 for mm in matches:
405 mm.second.set(self.usedKey, True)
406
407 # Prepare for fitting
408 sourceKeys = self.getSourceKeys(matches[0].second.schema)
409 arrays = self.extractMagArrays(matches, filterLabel, sourceKeys)
410
411 # Fit for zeropoint
412 r = self.getZeroPoint(arrays.srcMag, arrays.refMag, arrays.magErr)
413 self.log.info("Magnitude zero point: %f +/- %f from %d stars", r.zp, r.sigma, r.ngood)
414
415 # Prepare the results
416 flux0 = 10**(0.4*r.zp) # Flux of mag=0 star
417 flux0err = 0.4*math.log(10)*flux0*r.sigma # Error in flux0
418 photoCalib = makePhotoCalibFromCalibZeroPoint(flux0, flux0err)
419
420 return pipeBase.Struct(
421 photoCalib=photoCalib,
422 arrays=arrays,
423 matches=matches,
424 zp=r.zp,
425 sigma=r.sigma,
426 ngood=r.ngood,
427 )
428
429 def displaySources(self, exposure, matches, reserved, frame=1):
430 """Display sources we'll use for photocal.
431
432 Sources that will be actually used will be green.
433 Sources reserved from the fit will be red.
434
435 Parameters
436 ----------
437 exposure : `lsst.afw.image.ExposureF`
438 Exposure to display.
439 matches : `list` of `lsst.afw.table.RefMatch`
440 Matches used for photocal.
441 reserved : `numpy.ndarray` of type `bool`
442 Boolean array indicating sources that are reserved.
443 frame : `int`, optional
444 Frame number for display.
445 """
446 disp = afwDisplay.getDisplay(frame=frame)
447 disp.mtv(exposure, title="photocal")
448 with disp.Buffering():
449 for mm, rr in zip(matches, reserved):
450 x, y = mm.second.getCentroid()
451 ctype = afwDisplay.RED if rr else afwDisplay.GREEN
452 disp.dot("o", x, y, size=4, ctype=ctype)
453
454 def getZeroPoint(self, src, ref, srcErr=None, zp0=None):
455 """Flux calibration code, returning (ZeroPoint, Distribution Width, Number of stars).
456
457 Returns
458 -------
459 result : `lsst.pipe.base.Struct`
460 Results as a struct with attributes:
461
462 ``zp``
463 Photometric zero point (mag, `float`).
464 ``sigma``
465 Standard deviation of fit of photometric zero point (mag, `float`).
466 ``ngood``
467 Number of sources used to fit photometric zero point (`int`).
468
469 Notes
470 -----
471 We perform nIter iterations of a simple sigma-clipping algorithm with a couple of twists:
472 - We use the median/interquartile range to estimate the position to clip around, and the
473 "sigma" to use.
474 - We never allow sigma to go _above_ a critical value sigmaMax --- if we do, a sufficiently
475 large estimate will prevent the clipping from ever taking effect.
476 - Rather than start with the median we start with a crude mode. This means that a set of magnitude
477 residuals with a tight core and asymmetrical outliers will start in the core. We use the width of
478 this core to set our maximum sigma (see second bullet).
479 """
480 sigmaMax = self.config.sigmaMax
481
482 dmag = ref - src
483
484 indArr = np.argsort(dmag)
485 dmag = dmag[indArr]
486
487 if srcErr is not None:
488 dmagErr = srcErr[indArr]
489 else:
490 dmagErr = np.ones(len(dmag))
491
492 # need to remove nan elements to avoid errors in stats calculation with numpy
493 ind_noNan = np.array([i for i in range(len(dmag))
494 if (not np.isnan(dmag[i]) and not np.isnan(dmagErr[i]))])
495 dmag = dmag[ind_noNan]
496 dmagErr = dmagErr[ind_noNan]
497
498 IQ_TO_STDEV = 0.741301109252802 # 1 sigma in units of interquartile (assume Gaussian)
499
500 npt = len(dmag)
501 ngood = npt
502 good = None # set at end of first iteration
503 for i in range(self.config.nIter):
504 if i > 0:
505 npt = sum(good)
506
507 center = None
508 if i == 0:
509 #
510 # Start by finding the mode
511 #
512 nhist = 20
513 try:
514 hist, edges = np.histogram(dmag, nhist, new=True)
515 except TypeError:
516 hist, edges = np.histogram(dmag, nhist) # they removed new=True around numpy 1.5
517 imode = np.arange(nhist)[np.where(hist == hist.max())]
518
519 if imode[-1] - imode[0] + 1 == len(imode): # Multiple modes, but all contiguous
520 if zp0:
521 center = zp0
522 else:
523 center = 0.5*(edges[imode[0]] + edges[imode[-1] + 1])
524
525 peak = sum(hist[imode])/len(imode) # peak height
526
527 # Estimate FWHM of mode
528 j = imode[0]
529 while j >= 0 and hist[j] > 0.5*peak:
530 j -= 1
531 j = max(j, 0)
532 q1 = dmag[sum(hist[range(j)])]
533
534 j = imode[-1]
535 while j < nhist and hist[j] > 0.5*peak:
536 j += 1
537 j = min(j, nhist - 1)
538 j = min(sum(hist[range(j)]), npt - 1)
539 q3 = dmag[j]
540
541 if q1 == q3:
542 q1 = dmag[int(0.25*npt)]
543 q3 = dmag[int(0.75*npt)]
544
545 sig = (q3 - q1)/2.3 # estimate of standard deviation (based on FWHM; 2.358 for Gaussian)
546
547 if sigmaMax is None:
548 sigmaMax = 2*sig # upper bound on st. dev. for clipping. multiplier is a heuristic
549
550 self.log.debug("Photo calibration histogram: center = %.2f, sig = %.2f", center, sig)
551
552 else:
553 if sigmaMax is None:
554 sigmaMax = dmag[-1] - dmag[0]
555
556 center = np.median(dmag)
557 q1 = dmag[int(0.25*npt)]
558 q3 = dmag[int(0.75*npt)]
559 sig = (q3 - q1)/2.3 # estimate of standard deviation (based on FWHM; 2.358 for Gaussian)
560
561 if center is None: # usually equivalent to (i > 0)
562 gdmag = dmag[good]
563 if self.config.useMedian:
564 center = np.median(gdmag)
565 else:
566 gdmagErr = dmagErr[good]
567 center = np.average(gdmag, weights=gdmagErr)
568
569 q3 = gdmag[min(int(0.75*npt + 0.5), npt - 1)]
570 q1 = gdmag[min(int(0.25*npt + 0.5), npt - 1)]
571
572 sig = IQ_TO_STDEV*(q3 - q1) # estimate of standard deviation
573
574 good = abs(dmag - center) < self.config.nSigma*min(sig, sigmaMax) # don't clip too softly
575
576 # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
577 if self.scatterPlot:
578 try:
579 self.fig.clf()
580
581 axes = self.fig.add_axes((0.1, 0.1, 0.85, 0.80))
582
583 axes.plot(ref[good], dmag[good] - center, "b+")
584 axes.errorbar(ref[good], dmag[good] - center, yerr=dmagErr[good],
585 linestyle='', color='b')
586
587 bad = np.logical_not(good)
588 if len(ref[bad]) > 0:
589 axes.plot(ref[bad], dmag[bad] - center, "r+")
590 axes.errorbar(ref[bad], dmag[bad] - center, yerr=dmagErr[bad],
591 linestyle='', color='r')
592
593 axes.plot((-100, 100), (0, 0), "g-")
594 for x in (-1, 1):
595 axes.plot((-100, 100), x*0.05*np.ones(2), "g--")
596
597 axes.set_ylim(-1.1, 1.1)
598 axes.set_xlim(24, 13)
599 axes.set_xlabel("Reference")
600 axes.set_ylabel("Reference - Instrumental")
601
602 self.fig.show()
603
604 if self.scatterPlot > 1:
605 reply = None
606 while i == 0 or reply != "c":
607 try:
608 reply = input("Next iteration? [ynhpc] ")
609 except EOFError:
610 reply = "n"
611
612 if reply == "h":
613 print("Options: c[ontinue] h[elp] n[o] p[db] y[es]", file=sys.stderr)
614 continue
615
616 if reply in ("", "c", "n", "p", "y"):
617 break
618 else:
619 print("Unrecognised response: %s" % reply, file=sys.stderr)
620
621 if reply == "n":
622 break
623 elif reply == "p":
624 import pdb
625 pdb.set_trace()
626 except Exception as e:
627 print("Error plotting in PhotoCal.getZeroPoint: %s" % e, file=sys.stderr)
628
629 # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
630
631 old_ngood = ngood
632 ngood = sum(good)
633 if ngood == 0:
634 msg = "PhotoCal.getZeroPoint: no good stars remain"
635
636 if i == 0: # failed the first time round -- probably all fell in one bin
637 center = np.average(dmag, weights=dmagErr)
638 msg += " on first iteration; using average of all calibration stars"
639
640 self.log.warning(msg)
641
642 return pipeBase.Struct(
643 zp=center,
644 sigma=sig,
645 ngood=len(dmag))
646 elif ngood == old_ngood:
647 break
648
649 if False:
650 ref = ref[good]
651 dmag = dmag[good]
652 dmagErr = dmagErr[good]
653
654 dmag = dmag[good]
655 dmagErr = dmagErr[good]
656 zp, weightSum = np.average(dmag, weights=1/dmagErr**2, returned=True)
657 sigma = np.sqrt(1.0/weightSum)
658 return pipeBase.Struct(
659 zp=zp,
660 sigma=sigma,
661 ngood=len(dmag),
662 )
int min
int max
A class to contain the data, WCS, and other information needed to describe an image of the sky.
Definition: Exposure.h:72
Defines the fields and offsets for a table.
Definition: Schema.h:51
Record class that must contain a unique ID field and a celestial coordinate field.
Definition: Simple.h:48
Custom catalog class for record/table subclasses that are guaranteed to have an ID,...
Definition: SortedCatalog.h:42
Record class that contains measurements made on a single exposure.
Definition: Source.h:78
def getZeroPoint(self, src, ref, srcErr=None, zp0=None)
Definition: photoCal.py:454
def extractMagArrays(self, matches, filterLabel, sourceKeys)
Definition: photoCal.py:205
def __init__(self, refObjLoader, schema=None, **kwds)
Definition: photoCal.py:167
def displaySources(self, exposure, matches, reserved, frame=1)
Definition: photoCal.py:429
Lightweight representation of a geometric match between two records.
Definition: Match.h:67