24 from __future__
import absolute_import, division, print_function
25 from builtins
import zip
26 from builtins
import input
27 from builtins
import range
35 import lsst.pex.config
as pexConf
36 import lsst.pipe.base
as pipeBase
37 import lsst.afw.table
as afwTable
38 from lsst.afw.image
import abMagFromFlux, abMagErrFromFluxErr, fluxFromABMag, Calib
39 from lsst.meas.astrom
import RefMatchTask, RefMatchConfig
40 import lsst.afw.display.ds9
as ds9
41 from lsst.meas.algorithms
import getRefFluxField
42 from .colorterms
import ColortermLibrary
44 __all__ = [
"PhotoCalTask",
"PhotoCalConfig"]
48 """!Return True if the given source has all good flags set and none of the bad flags set. 50 \param[in] source SourceRecord object to process. 51 \param[in] sourceKeys Struct of source catalog keys, as returned by PhotCalTask.getSourceKeys() 53 for k
in sourceKeys.goodFlags:
56 if source.getPsfFluxFlag():
58 for k
in sourceKeys.badFlags:
65 """Config for PhotoCal""" 66 magLimit = pexConf.Field(
69 doc=
"Don't use objects fainter than this magnitude",
71 reserveFraction = pexConf.Field(
73 doc=
"Fraction of candidates to reserve from fitting; none if <= 0",
76 reserveSeed = pexConf.Field(
78 doc =
"This number will be multiplied by the exposure ID " 79 "to set the random seed for reserving candidates",
82 fluxField = pexConf.Field(
84 default=
"slot_CalibFlux_flux",
85 doc=(
"Name of the source flux field to use. The associated flag field\n" 86 "('<name>_flags') will be implicitly included in badFlags."),
88 applyColorTerms = pexConf.Field(
91 doc=(
"Apply photometric color terms to reference stars? One of:\n" 92 "None: apply if colorterms and photoCatName are not None;\n" 93 " fail if color term data is not available for the specified ref catalog and filter.\n" 94 "True: always apply colorterms; fail if color term data is not available for the\n" 95 " specified reference catalog and filter.\n" 96 "False: do not apply."),
99 goodFlags = pexConf.ListField(
102 doc=
"List of source flag fields that must be set for a source to be used.",
104 badFlags = pexConf.ListField(
106 default=[
"base_PixelFlags_flag_edge",
"base_PixelFlags_flag_interpolated",
107 "base_PixelFlags_flag_saturated"],
108 doc=
"List of source flag fields that will cause a source to be rejected when they are set.",
110 sigmaMax = pexConf.Field(
113 doc=
"maximum sigma to use when clipping",
116 nSigma = pexConf.Field(
119 doc=
"clip at nSigma",
121 useMedian = pexConf.Field(
124 doc=
"use median instead of mean to compute zeropoint",
126 nIter = pexConf.Field(
129 doc=
"number of iterations",
131 colorterms = pexConf.ConfigField(
132 dtype=ColortermLibrary,
133 doc=
"Library of photometric reference catalog name: color term dict",
135 photoCatName = pexConf.Field(
138 doc=(
"Name of photometric reference catalog; used to select a color term dict in colorterms." 139 " see also applyColorTerms"),
141 magErrFloor = pexConf.RangeField(
144 doc=
"Additional magnitude uncertainty to be added in quadrature with measurement errors.",
147 doSelectUnresolved = pexConf.Field(
150 doc=(
"Use the extendedness parameter to select objects to use in photometric calibration?\n" 151 "This applies only to the sources detected on the exposure, not the reference catalog"),
155 pexConf.Config.validate(self)
157 raise RuntimeError(
"applyColorTerms=True requires photoCatName is non-None")
159 raise RuntimeError(
"applyColorTerms=True requires colorterms be provided")
171 \anchor PhotoCalTask_ 173 \brief Calculate the zero point of an exposure given a lsst.afw.table.ReferenceMatchVector. 175 \section pipe_tasks_photocal_Contents Contents 177 - \ref pipe_tasks_photocal_Purpose 178 - \ref pipe_tasks_photocal_Initialize 179 - \ref pipe_tasks_photocal_IO 180 - \ref pipe_tasks_photocal_Config 181 - \ref pipe_tasks_photocal_Debug 182 - \ref pipe_tasks_photocal_Example 184 \section pipe_tasks_photocal_Purpose Description 186 \copybrief PhotoCalTask 188 Calculate an Exposure's zero-point given a set of flux measurements of stars matched to an input catalogue. 189 The type of flux to use is specified by PhotoCalConfig.fluxField. 191 The algorithm clips outliers iteratively, with parameters set in the configuration. 193 \note This task can adds fields to the schema, so any code calling this task must ensure that 194 these columns are indeed present in the input match list; see \ref pipe_tasks_photocal_Example 196 \section pipe_tasks_photocal_Initialize Task initialisation 198 \copydoc \_\_init\_\_ 200 \section pipe_tasks_photocal_IO Inputs/Outputs to the run method 204 \section pipe_tasks_photocal_Config Configuration parameters 206 See \ref PhotoCalConfig 208 \section pipe_tasks_photocal_Debug Debug variables 210 The \link lsst.pipe.base.cmdLineTask.CmdLineTask command line task\endlink interface supports a 211 flag \c -d to import \b debug.py from your \c PYTHONPATH; see \ref baseDebug for more about \b debug.py files. 213 The available variables in PhotoCalTask are: 216 <DD> If True enable other debug outputs 217 <DT> \c displaySources 218 <DD> If True, display the exposure on ds9's frame 1 and overlay the source catalogue: 223 <DD> Matched objects deemed unsuitable for photometric calibration. 224 Additional information is: 225 - a cyan o for galaxies 226 - a magenta o for variables 228 <DD> Objects that failed the flux cut 230 <DD> Objects used in the photometric calibration 233 <DD> Make a scatter plot of flux v. reference magnitude as a function of reference magnitude. 234 - good objects in blue 235 - rejected objects in red 236 (if \c scatterPlot is 2 or more, prompt to continue after each iteration) 239 \section pipe_tasks_photocal_Example A complete example of using PhotoCalTask 241 This code is in \link examples/photoCalTask.py\endlink, and can be run as \em e.g. 243 examples/photoCalTask.py 245 \dontinclude photoCalTask.py 247 Import the tasks (there are some other standard imports; read the file for details) 248 \skipline from lsst.pipe.tasks.astrometry 249 \skipline measPhotocal 251 We need to create both our tasks before processing any data as the task constructors 252 can add extra columns to the schema which we get from the input catalogue, \c scrCat: 256 \skip AstrometryTask.ConfigClass 258 (that \c filterMap line is because our test code doesn't use a filter that the reference catalogue recognises, 259 so we tell it to use the \c r band) 265 If the schema has indeed changed we need to add the new columns to the source table 266 (yes; this should be easier!) 270 We're now ready to process the data (we could loop over multiple exposures/catalogues using the same 275 We can then unpack and use the results: 280 To investigate the \ref pipe_tasks_photocal_Debug, put something like 284 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively 285 if name.endswith(".PhotoCal"): 290 lsstDebug.Info = DebugInfo 292 into your debug.py file and run photoCalTask.py with the \c --debug flag. 294 ConfigClass = PhotoCalConfig
295 _DefaultName =
"photoCal" 297 def __init__(self, refObjLoader, schema=None, **kwds):
298 """!Create the photometric calibration task. See PhotoCalTask.init for documentation 300 RefMatchTask.__init__(self, refObjLoader, schema=
None, **kwds)
303 if schema
is not None:
304 self.
usedKey = schema.addField(
"calib_photometryUsed", type=
"Flag",
305 doc=
"set if source was used in photometric calibration")
306 self.
candidateKey = schema.addField(
"calib_photometryCandidate", type=
"Flag",
307 doc=
"set if source was a candidate for use in calibration")
308 self.
reservedKey = schema.addField(
"calib_photometryReserved", type=
"Flag",
309 doc=
"set if source was reserved, so not used in calibration")
316 """!Return a struct containing the source catalog keys for fields used by PhotoCalTask. 318 Returned fields include: 321 - goodFlags: a list of keys for field names in self.config.goodFlags 322 - badFlags: a list of keys for field names in self.config.badFlags 323 - starGal: key for star/galaxy classification 325 goodFlags = [schema.find(name).key
for name
in self.config.goodFlags]
326 flux = schema.find(self.config.fluxField).key
327 fluxErr = schema.find(self.config.fluxField +
"Sigma").key
328 badFlags = [schema.find(name).key
for name
in self.config.badFlags]
330 starGal = schema.find(
"base_ClassificationExtendedness_value").key
333 return pipeBase.Struct(flux=flux, fluxErr=fluxErr, goodFlags=goodFlags, badFlags=badFlags,
337 """!Return whether the provided source is unresolved or not 339 This particular implementation is designed to work with the 340 base_ClassificationExtendedness_value=0.0 or 1.0 scheme. Because 341 of the diversity of star/galaxy classification outputs (binary 342 decision vs probabilities; signs), it's difficult to make this 343 configurable without using code. This method should therefore 344 be overridden to use the appropriate classification output. 346 \param[in] source Source to test 347 \param[in] starGalKey Struct of schema keys for source 348 \return boolean value for starGalKey (True indicates Unresolved) 350 return source.get(starGalKey) < 0.5
if starGalKey
is not None else True 354 """!Select reference/source matches according the criteria specified in the config. 356 \param[in] matches ReferenceMatchVector (not modified) 357 \param[in] sourceKeys Struct of source catalog keys, as returned by getSourceKeys() 358 \param[in] filterName name of camera filter; used to obtain the reference flux field 359 \param[in] frame ds9 frame number to use for debugging display 360 if frame is non-None, display information about trimmed objects on that ds9 frame: 362 - Unsuitable objects: blue + (and a cyan o if a galaxy) 363 - Failed flux cut: magenta * 365 \return a \link lsst.afw.table.ReferenceMatchVector\endlink that contains only the selected matches. 366 If a schema was passed during task construction, a flag field will be set on sources 367 in the selected matches. 369 \throws ValueError There are no valid matches. 371 self.log.debug(
"Number of input matches: %d", len(matches))
373 if self.config.doSelectUnresolved:
375 matches = [m
for m
in matches
if self.
isUnresolved(m.second, sourceKeys.starGal)]
376 self.log.debug(
"Number of matches after culling resolved sources: %d", len(matches))
378 if len(matches) == 0:
379 raise ValueError(
"No input matches")
386 afterFlagCutInd = [i
for i, m
in enumerate(matches)
if checkSourceFlags(m.second, sourceKeys)]
387 afterFlagCut = [matches[i]
for i
in afterFlagCutInd]
388 self.log.debug(
"Number of matches after source flag cuts: %d", len(afterFlagCut))
390 if len(afterFlagCut) != len(matches):
391 if frame
is not None:
392 with ds9.Buffering():
393 for i, m
in enumerate(matches):
394 if i
not in afterFlagCutInd:
395 x, y = m.second.getCentroid()
396 ds9.dot(
"x", x, y, size=4, frame=frame, ctype=ds9.RED)
398 matches = afterFlagCut
400 if len(matches) == 0:
401 raise ValueError(
"All matches eliminated by source flags")
403 refSchema = matches[0].first.schema
405 photometricKey = refSchema.find(
"photometric").key
407 resolvedKey = refSchema.find(
"resolved").key
412 variableKey = refSchema.find(
"variable").key
416 self.log.warn(
"No 'photometric' flag key found in reference schema.")
417 photometricKey =
None 419 if photometricKey
is not None:
420 afterRefCutInd = [i
for i, m
in enumerate(matches)
if m.first.get(photometricKey)]
421 afterRefCut = [matches[i]
for i
in afterRefCutInd]
423 if len(afterRefCut) != len(matches):
424 if frame
is not None:
425 with ds9.Buffering():
426 for i, m
in enumerate(matches):
427 if i
not in afterRefCutInd:
428 x, y = m.second.getCentroid()
429 ds9.dot(
"+", x, y, size=4, frame=frame, ctype=ds9.BLUE)
431 if resolvedKey
and m.first.get(resolvedKey):
432 ds9.dot(
"o", x, y, size=6, frame=frame, ctype=ds9.CYAN)
433 if variableKey
and m.first.get(variableKey):
434 ds9.dot(
"o", x, y, size=6, frame=frame, ctype=ds9.MAGENTA)
436 matches = afterRefCut
438 self.log.debug(
"Number of matches after reference catalog cuts: %d", len(matches))
439 if len(matches) == 0:
440 raise RuntimeError(
"No sources remain in match list after reference catalog cuts.")
441 fluxName = getRefFluxField(refSchema, filterName)
442 fluxKey = refSchema.find(fluxName).key
443 if self.config.magLimit
is not None:
444 fluxLimit = fluxFromABMag(self.config.magLimit)
446 afterMagCutInd = [i
for i, m
in enumerate(matches)
if (m.first.get(fluxKey) > fluxLimit
and 447 m.second.getPsfFlux() > 0.0)]
449 afterMagCutInd = [i
for i, m
in enumerate(matches)
if m.second.getPsfFlux() > 0.0]
451 afterMagCut = [matches[i]
for i
in afterMagCutInd]
453 if len(afterMagCut) != len(matches):
454 if frame
is not None:
455 with ds9.Buffering():
456 for i, m
in enumerate(matches):
457 if i
not in afterMagCutInd:
458 x, y = m.second.getCentroid()
459 ds9.dot(
"*", x, y, size=4, frame=frame, ctype=ds9.MAGENTA)
461 matches = afterMagCut
463 self.log.debug(
"Number of matches after magnitude limit cuts: %d", len(matches))
465 if len(matches) == 0:
466 raise RuntimeError(
"No sources remaining in match list after magnitude limit cuts.")
468 if frame
is not None:
469 with ds9.Buffering():
471 x, y = m.second.getCentroid()
472 ds9.dot(
"o", x, y, size=4, frame=frame, ctype=ds9.GREEN)
477 m.second.set(self.
usedKey,
True)
483 """!Extract magnitude and magnitude error arrays from the given matches. 485 \param[in] matches Reference/source matches, a \link lsst::afw::table::ReferenceMatchVector\endlink 486 \param[in] filterName Name of filter being calibrated 487 \param[in] sourceKeys Struct of source catalog keys, as returned by getSourceKeys() 489 \return Struct containing srcMag, refMag, srcMagErr, refMagErr, and magErr numpy arrays 490 where magErr is an error in the magnitude; the error in srcMag - refMag 491 If nonzero, config.magErrFloor will be added to magErr *only* (not srcMagErr or refMagErr), as 492 magErr is what is later used to determine the zero point. 493 Struct also contains refFluxFieldList: a list of field names of the reference catalog used for fluxes 495 \note These magnitude arrays are the \em inputs to the photometric calibration, some may have been 496 discarded by clipping while estimating the calibration (https://jira.lsstcorp.org/browse/DM-813) 498 srcFluxArr = np.array([m.second.get(sourceKeys.flux)
for m
in matches])
499 srcFluxErrArr = np.array([m.second.get(sourceKeys.fluxErr)
for m
in matches])
500 if not np.all(np.isfinite(srcFluxErrArr)):
502 self.log.warn(
"Source catalog does not have flux uncertainties; using sqrt(flux).")
503 srcFluxErrArr = np.sqrt(srcFluxArr)
506 JanskysPerABFlux = 3631.0
507 srcFluxArr = srcFluxArr * JanskysPerABFlux
508 srcFluxErrArr = srcFluxErrArr * JanskysPerABFlux
511 raise RuntimeError(
"No reference stars are available")
512 refSchema = matches[0].first.schema
514 applyColorTerms = self.config.applyColorTerms
515 applyCTReason =
"config.applyColorTerms is %s" % (self.config.applyColorTerms,)
516 if self.config.applyColorTerms
is None:
518 ctDataAvail = len(self.config.colorterms.data) > 0
519 photoCatSpecified = self.config.photoCatName
is not None 520 applyCTReason +=
" and data %s available" % (
"is" if ctDataAvail
else "is not")
521 applyCTReason +=
" and photoRefCat %s None" % (
"is not" if photoCatSpecified
else "is")
522 applyColorTerms = ctDataAvail
and photoCatSpecified
525 self.log.info(
"Applying color terms for filterName=%r, config.photoCatName=%s because %s",
526 filterName, self.config.photoCatName, applyCTReason)
527 ct = self.config.colorterms.getColorterm(
528 filterName=filterName, photoCatName=self.config.photoCatName, doRaise=
True)
530 self.log.info(
"Not applying color terms because %s", applyCTReason)
534 fluxFieldList = [getRefFluxField(refSchema, filt)
for filt
in (ct.primary, ct.secondary)]
535 missingFluxFieldList = []
536 for fluxField
in fluxFieldList:
538 refSchema.find(fluxField).key
540 missingFluxFieldList.append(fluxField)
542 if missingFluxFieldList:
543 self.log.warn(
"Source catalog does not have fluxes for %s; ignoring color terms",
544 " ".join(missingFluxFieldList))
548 fluxFieldList = [getRefFluxField(refSchema, filterName)]
551 refFluxErrArrList = []
552 for fluxField
in fluxFieldList:
553 fluxKey = refSchema.find(fluxField).key
554 refFluxArr = np.array([m.first.get(fluxKey)
for m
in matches])
556 fluxErrKey = refSchema.find(fluxField +
"Sigma").key
557 refFluxErrArr = np.array([m.first.get(fluxErrKey)
for m
in matches])
560 self.log.warn(
"Reference catalog does not have flux uncertainties for %s; using sqrt(flux).",
562 refFluxErrArr = np.sqrt(refFluxArr)
564 refFluxArrList.append(refFluxArr)
565 refFluxErrArrList.append(refFluxErrArr)
568 refMagArr1 = np.array([abMagFromFlux(rf1)
for rf1
in refFluxArrList[0]])
569 refMagArr2 = np.array([abMagFromFlux(rf2)
for rf2
in refFluxArrList[1]])
571 refMagArr = ct.transformMags(refMagArr1, refMagArr2)
572 refFluxErrArr = ct.propagateFluxErrors(refFluxErrArrList[0], refFluxErrArrList[1])
574 refMagArr = np.array([abMagFromFlux(rf)
for rf
in refFluxArrList[0]])
576 srcMagArr = np.array([abMagFromFlux(sf)
for sf
in srcFluxArr])
580 magErrArr = np.array([abMagErrFromFluxErr(fe, sf)
for fe, sf
in zip(srcFluxErrArr, srcFluxArr)])
581 if self.config.magErrFloor != 0.0:
582 magErrArr = (magErrArr**2 + self.config.magErrFloor**2)**0.5
584 srcMagErrArr = np.array([abMagErrFromFluxErr(sfe, sf)
for sfe, sf
in zip(srcFluxErrArr, srcFluxArr)])
585 refMagErrArr = np.array([abMagErrFromFluxErr(rfe, rf)
for rfe, rf
in zip(refFluxErrArr, refFluxArr)])
587 return pipeBase.Struct(
591 srcMagErr=srcMagErrArr,
592 refMagErr=refMagErrArr,
593 refFluxFieldList=fluxFieldList,
597 def run(self, exposure, sourceCat, expId=0):
598 """!Do photometric calibration - select matches to use and (possibly iteratively) compute 601 \param[in] exposure Exposure upon which the sources in the matches were detected. 602 \param[in] sourceCat A catalog of sources to use in the calibration 603 (\em i.e. a list of lsst.afw.table.Match with 604 \c first being of type lsst.afw.table.SimpleRecord and \c second type lsst.afw.table.SourceRecord --- 605 the reference object and matched object respectively). 606 (will not be modified except to set the outputField if requested.). 609 - calib ------- \link lsst::afw::image::Calib\endlink object containing the zero point 610 - arrays ------ Magnitude arrays returned be PhotoCalTask.extractMagArrays 611 - matches ----- Final ReferenceMatchVector, as returned by PhotoCalTask.selectMatches. 612 - zp ---------- Photometric zero point (mag) 613 - sigma ------- Standard deviation of fit of photometric zero point (mag) 614 - ngood ------- Number of sources used to fit photometric zero point 616 The exposure is only used to provide the name of the filter being calibrated (it may also be 617 used to generate debugging plots). 619 The reference objects: 620 - Must include a field \c photometric; True for objects which should be considered as 621 photometric standards 622 - Must include a field \c flux; the flux used to impose a magnitude limit and also to calibrate 623 the data to (unless a color term is specified, in which case ColorTerm.primary is used; 624 See https://jira.lsstcorp.org/browse/DM-933) 625 - May include a field \c stargal; if present, True means that the object is a star 626 - May include a field \c var; if present, True means that the object is variable 628 The measured sources: 629 - Must include PhotoCalConfig.fluxField; the flux measurement to be used for calibration 631 \throws RuntimeError with the following strings: 634 <DT> `sources' schema does not contain the calibration object flag "XXX"` 635 <DD> The constructor added fields to the schema that aren't in the Sources 636 <DT> No input matches 637 <DD> The input match vector is empty 638 <DT> All matches eliminated by source flags 639 <DD> The flags specified by \c badFlags in the config eliminated all candidate objects 640 <DT> No sources remain in match list after reference catalog cuts 641 <DD> The reference catalogue has a column "photometric", but no matched objects have it set 642 <DT> No sources remaining in match list after magnitude limit cuts 643 <DD> All surviving matches are either too faint in the catalogue or have negative or \c NaN flux 644 <DT> No reference stars are available 645 <DD> No matches survive all the checks 650 display = lsstDebug.Info(__name__).display
651 displaySources = display
and lsstDebug.Info(__name__).displaySources
652 self.
scatterPlot = display
and lsstDebug.Info(__name__).scatterPlot
655 from matplotlib
import pyplot
659 self.
fig = pyplot.figure()
663 ds9.mtv(exposure, frame=frame, title=
"photocal")
667 res = self.loadAndMatch(exposure, sourceCat)
671 if self.config.reserveFraction > 0:
672 random.seed(self.config.reserveSeed*expId)
673 reserveList = random.sample(res.matches,
674 int((self.config.reserveFraction)*len(res.matches)))
676 for candidate
in reserveList:
677 res.matches.remove(candidate)
680 for candidate
in reserveList:
683 matches = res.matches
688 filterName = exposure.getFilter().getName()
691 matches = self.
selectMatches(matches=matches, sourceKeys=sourceKeys, filterName=filterName,
693 arrays = self.
extractMagArrays(matches=matches, filterName=filterName, sourceKeys=sourceKeys)
699 matches[0].second.getSchema().find(self.
usedKey)
701 raise RuntimeError(
"sources' schema does not contain the calib_photometryUsed flag \"%s\"" %
710 r = self.
getZeroPoint(arrays.srcMag, arrays.refMag, arrays.magErr, zp0=zp)
712 self.log.info(
"Magnitude zero point: %f +/- %f from %d stars", r.zp, r.sigma, r.ngood)
714 flux0 = 10**(0.4*r.zp)
715 flux0err = 0.4*math.log(10)*flux0*r.sigma
717 calib.setFluxMag0(flux0, flux0err)
719 return pipeBase.Struct(
729 """!Flux calibration code, returning (ZeroPoint, Distribution Width, Number of stars) 731 We perform nIter iterations of a simple sigma-clipping algorithm with a couple of twists: 732 1. We use the median/interquartile range to estimate the position to clip around, and the 734 2. We never allow sigma to go _above_ a critical value sigmaMax --- if we do, a sufficiently 735 large estimate will prevent the clipping from ever taking effect. 736 3. Rather than start with the median we start with a crude mode. This means that a set of magnitude 737 residuals with a tight core and asymmetrical outliers will start in the core. We use the width of 738 this core to set our maximum sigma (see 2.) 741 - zp ---------- Photometric zero point (mag) 742 - sigma ------- Standard deviation of fit of zero point (mag) 743 - ngood ------- Number of sources used to fit zero point 745 sigmaMax = self.config.sigmaMax
749 indArr = np.argsort(dmag)
752 if srcErr
is not None:
753 dmagErr = srcErr[indArr]
755 dmagErr = np.ones(len(dmag))
758 ind_noNan = np.array([i
for i
in range(len(dmag))
759 if (
not np.isnan(dmag[i])
and not np.isnan(dmagErr[i]))])
760 dmag = dmag[ind_noNan]
761 dmagErr = dmagErr[ind_noNan]
763 IQ_TO_STDEV = 0.741301109252802
768 for i
in range(self.config.nIter):
779 hist, edges = np.histogram(dmag, nhist, new=
True)
781 hist, edges = np.histogram(dmag, nhist)
782 imode = np.arange(nhist)[np.where(hist == hist.max())]
784 if imode[-1] - imode[0] + 1 == len(imode):
788 center = 0.5*(edges[imode[0]] + edges[imode[-1] + 1])
790 peak = sum(hist[imode])/len(imode)
794 while j >= 0
and hist[j] > 0.5*peak:
797 q1 = dmag[sum(hist[range(j)])]
800 while j < nhist
and hist[j] > 0.5*peak:
802 j = min(j, nhist - 1)
803 j = min(sum(hist[range(j)]), npt - 1)
807 q1 = dmag[int(0.25*npt)]
808 q3 = dmag[int(0.75*npt)]
815 self.log.debug(
"Photo calibration histogram: center = %.2f, sig = %.2f", center, sig)
819 sigmaMax = dmag[-1] - dmag[0]
821 center = np.median(dmag)
822 q1 = dmag[int(0.25*npt)]
823 q3 = dmag[int(0.75*npt)]
828 if self.config.useMedian:
829 center = np.median(gdmag)
831 gdmagErr = dmagErr[good]
832 center = np.average(gdmag, weights=gdmagErr)
834 q3 = gdmag[min(int(0.75*npt + 0.5), npt - 1)]
835 q1 = gdmag[min(int(0.25*npt + 0.5), npt - 1)]
837 sig = IQ_TO_STDEV*(q3 - q1)
839 good = abs(dmag - center) < self.config.nSigma*min(sig, sigmaMax)
846 axes = self.
fig.add_axes((0.1, 0.1, 0.85, 0.80))
848 axes.plot(ref[good], dmag[good] - center,
"b+")
849 axes.errorbar(ref[good], dmag[good] - center, yerr=dmagErr[good],
850 linestyle=
'', color=
'b')
852 bad = np.logical_not(good)
853 if len(ref[bad]) > 0:
854 axes.plot(ref[bad], dmag[bad] - center,
"r+")
855 axes.errorbar(ref[bad], dmag[bad] - center, yerr=dmagErr[bad],
856 linestyle=
'', color=
'r') 858 axes.plot((-100, 100), (0, 0), "g-")
860 axes.plot((-100, 100), x*0.05*np.ones(2),
"g--")
862 axes.set_ylim(-1.1, 1.1)
863 axes.set_xlim(24, 13)
864 axes.set_xlabel(
"Reference")
865 axes.set_ylabel(
"Reference - Instrumental")
871 while i == 0
or reply !=
"c":
873 reply = input(
"Next iteration? [ynhpc] ")
878 print(
"Options: c[ontinue] h[elp] n[o] p[db] y[es]", file=sys.stderr)
881 if reply
in (
"",
"c",
"n",
"p",
"y"):
884 print(
"Unrecognised response: %s" % reply, file=sys.stderr)
891 except Exception
as e:
892 print(
"Error plotting in PhotoCal.getZeroPoint: %s" % e, file=sys.stderr)
899 msg =
"PhotoCal.getZeroPoint: no good stars remain" 902 center = np.average(dmag, weights=dmagErr)
903 msg +=
" on first iteration; using average of all calibration stars" 907 return pipeBase.Struct(
911 elif ngood == old_ngood:
917 dmagErr = dmagErr[good]
920 dmagErr = dmagErr[good]
921 zp, weightSum = np.average(dmag, weights=1/dmagErr**2, returned=
True)
922 sigma = np.sqrt(1.0/weightSum)
923 return pipeBase.Struct(
def __init__(self, refObjLoader, schema=None, kwds)
Create the photometric calibration task.
def run(self, exposure, sourceCat, expId=0)
Do photometric calibration - select matches to use and (possibly iteratively) compute the zero point...
def getSourceKeys(self, schema)
Return a struct containing the source catalog keys for fields used by PhotoCalTask.
def extractMagArrays(self, matches, filterName, sourceKeys)
Extract magnitude and magnitude error arrays from the given matches.
Calculate the zero point of an exposure given a lsst.afw.table.ReferenceMatchVector.
def checkSourceFlags(source, sourceKeys)
Return True if the given source has all good flags set and none of the bad flags set.
def getZeroPoint(self, src, ref, srcErr=None, zp0=None)
Flux calibration code, returning (ZeroPoint, Distribution Width, Number of stars) ...
def isUnresolved(self, source, starGalKey=None)
Return whether the provided source is unresolved or not.
def selectMatches(self, matches, sourceKeys, filterName, frame=None)
Select reference/source matches according the criteria specified in the config.