22__all__ = [
"CalibrateConfig",
"CalibrateTask"]
27from lsstDebug
import getDebugFrame
30import lsst.pipe.base.connectionTypes
as cT
32from lsst.meas.astrom import AstrometryTask, displayAstrometry, denormalizeMatches
40 CatalogCalculationTask,
42 DetectorVisitIdGeneratorConfig)
44from lsst.utils.timer
import timeMethod
46from .photoCal
import PhotoCalTask
47from .computeExposureSummaryStats
import ComputeExposureSummaryStatsTask
53 icSourceSchema = cT.InitInput(
54 doc=
"Schema produced by characterize image task, used to initialize this task",
56 storageClass=
"SourceCatalog",
59 outputSchema = cT.InitOutput(
60 doc=
"Schema after CalibrateTask has been initialized",
62 storageClass=
"SourceCatalog",
66 doc=
"Input image to calibrate",
68 storageClass=
"ExposureF",
69 dimensions=(
"instrument",
"visit",
"detector"),
72 background = cT.Input(
73 doc=
"Backgrounds determined by characterize task",
74 name=
"icExpBackground",
75 storageClass=
"Background",
76 dimensions=(
"instrument",
"visit",
"detector"),
79 icSourceCat = cT.Input(
80 doc=
"Source catalog created by characterize task",
82 storageClass=
"SourceCatalog",
83 dimensions=(
"instrument",
"visit",
"detector"),
86 astromRefCat = cT.PrerequisiteInput(
87 doc=
"Reference catalog to use for astrometry",
88 name=
"gaia_dr2_20200414",
89 storageClass=
"SimpleCatalog",
90 dimensions=(
"skypix",),
95 photoRefCat = cT.PrerequisiteInput(
96 doc=
"Reference catalog to use for photometric calibration",
97 name=
"ps1_pv3_3pi_20170110",
98 storageClass=
"SimpleCatalog",
99 dimensions=(
"skypix",),
104 outputExposure = cT.Output(
105 doc=
"Exposure after running calibration task",
107 storageClass=
"ExposureF",
108 dimensions=(
"instrument",
"visit",
"detector"),
111 outputCat = cT.Output(
112 doc=
"Source catalog produced in calibrate task",
114 storageClass=
"SourceCatalog",
115 dimensions=(
"instrument",
"visit",
"detector"),
118 outputBackground = cT.Output(
119 doc=
"Background models estimated in calibration task",
120 name=
"calexpBackground",
121 storageClass=
"Background",
122 dimensions=(
"instrument",
"visit",
"detector"),
126 doc=
"Source/refObj matches from the astrometry solver",
128 storageClass=
"Catalog",
129 dimensions=(
"instrument",
"visit",
"detector"),
132 matchesDenormalized = cT.Output(
133 doc=
"Denormalized matches from astrometry solver",
135 storageClass=
"Catalog",
136 dimensions=(
"instrument",
"visit",
"detector"),
142 if config.doAstrometry
is False:
143 self.prerequisiteInputs.remove(
"astromRefCat")
144 if config.doPhotoCal
is False:
145 self.prerequisiteInputs.remove(
"photoRefCat")
147 if config.doWriteMatches
is False or config.doAstrometry
is False:
148 self.outputs.remove(
"matches")
149 if config.doWriteMatchesDenormalized
is False or config.doAstrometry
is False:
150 self.outputs.remove(
"matchesDenormalized")
153class CalibrateConfig(pipeBase.PipelineTaskConfig, pipelineConnections=CalibrateConnections):
154 """Config for CalibrateTask."""
156 doWrite = pexConfig.Field(
159 doc=
"Save calibration results?",
161 doWriteHeavyFootprintsInSources = pexConfig.Field(
164 doc=
"Include HeavyFootprint data in source table? If false then heavy "
165 "footprints are saved as normal footprints, which saves some space"
167 doWriteMatches = pexConfig.Field(
170 doc=
"Write reference matches (ignored if doWrite or doAstrometry false)?",
172 doWriteMatchesDenormalized = pexConfig.Field(
175 doc=(
"Write reference matches in denormalized format? "
176 "This format uses more disk space, but is more convenient to "
177 "read. Ignored if doWriteMatches=False or doWrite=False."),
179 doAstrometry = pexConfig.Field(
182 doc=
"Perform astrometric calibration?",
184 astromRefObjLoader = pexConfig.ConfigField(
185 dtype=LoadReferenceObjectsConfig,
186 doc=
"reference object loader for astrometric calibration",
188 photoRefObjLoader = pexConfig.ConfigField(
189 dtype=LoadReferenceObjectsConfig,
190 doc=
"reference object loader for photometric calibration",
192 astrometry = pexConfig.ConfigurableField(
193 target=AstrometryTask,
194 doc=
"Perform astrometric calibration to refine the WCS",
196 requireAstrometry = pexConfig.Field(
199 doc=(
"Raise an exception if astrometry fails? Ignored if doAstrometry "
202 doPhotoCal = pexConfig.Field(
205 doc=
"Perform phometric calibration?",
207 requirePhotoCal = pexConfig.Field(
210 doc=(
"Raise an exception if photoCal fails? Ignored if doPhotoCal "
213 photoCal = pexConfig.ConfigurableField(
215 doc=
"Perform photometric calibration",
217 icSourceFieldsToCopy = pexConfig.ListField(
219 default=(
"calib_psf_candidate",
"calib_psf_used",
"calib_psf_reserved"),
220 doc=(
"Fields to copy from the icSource catalog to the output catalog "
221 "for matching sources Any missing fields will trigger a "
222 "RuntimeError exception. Ignored if icSourceCat is not provided.")
224 matchRadiusPix = pexConfig.Field(
227 doc=(
"Match radius for matching icSourceCat objects to sourceCat "
230 checkUnitsParseStrict = pexConfig.Field(
231 doc=(
"Strictness of Astropy unit compatibility check, can be 'raise', "
232 "'warn' or 'silent'"),
236 detection = pexConfig.ConfigurableField(
237 target=SourceDetectionTask,
240 doDeblend = pexConfig.Field(
243 doc=
"Run deblender input exposure"
245 deblend = pexConfig.ConfigurableField(
246 target=SourceDeblendTask,
247 doc=
"Split blended sources into their components"
249 doSkySources = pexConfig.Field(
252 doc=
"Generate sky sources?",
254 skySources = pexConfig.ConfigurableField(
255 target=SkyObjectsTask,
256 doc=
"Generate sky sources",
258 measurement = pexConfig.ConfigurableField(
259 target=SingleFrameMeasurementTask,
260 doc=
"Measure sources"
262 postCalibrationMeasurement = pexConfig.ConfigurableField(
263 target=SingleFrameMeasurementTask,
264 doc=
"Second round of measurement for plugins that need to be run after photocal"
266 setPrimaryFlags = pexConfig.ConfigurableField(
267 target=SetPrimaryFlagsTask,
268 doc=(
"Set flags for primary source classification in single frame "
269 "processing. True if sources are not sky sources and not a parent.")
271 doApCorr = pexConfig.Field(
274 doc=
"Run subtask to apply aperture correction"
276 applyApCorr = pexConfig.ConfigurableField(
277 target=ApplyApCorrTask,
278 doc=
"Subtask to apply aperture corrections"
283 catalogCalculation = pexConfig.ConfigurableField(
284 target=CatalogCalculationTask,
285 doc=
"Subtask to run catalogCalculation plugins on catalog"
287 doComputeSummaryStats = pexConfig.Field(
290 doc=
"Run subtask to measure exposure summary statistics?"
292 computeSummaryStats = pexConfig.ConfigurableField(
293 target=ComputeExposureSummaryStatsTask,
294 doc=
"Subtask to run computeSummaryStats on exposure"
296 doWriteExposure = pexConfig.Field(
299 doc=
"Write the calexp? If fakes have been added then we do not want to write out the calexp as a "
300 "normal calexp but as a fakes_calexp."
302 idGenerator = DetectorVisitIdGeneratorConfig.make_field()
306 self.
detection.doTempLocalBackground =
False
307 self.
deblend.maxFootprintSize = 2000
314 self.
photoCal.photoCatName = self.connections.photoRefCat
318 """Calibrate an exposure: measure sources and perform astrometric and
319 photometric calibration.
321 Given an exposure with a good PSF model
and aperture correction map(e.g.
as
323 perform the following operations:
324 - Run detection
and measurement
325 - Run astrometry subtask to fit an improved WCS
326 - Run photoCal subtask to fit the exposure
's photometric zero-point
331 Compatibility parameter. Should always be `
None`.
333 Unused
in gen3: must be `
None`.
335 Unused
in gen3: must be `
None`.
337 Schema
for the icSource catalog.
338 initInputs : `dict`, optional
339 Dictionary that can contain a key ``icSourceSchema`` containing the
340 input schema. If present will override the value of ``icSourceSchema``.
345 Raised
if any of the following occur:
346 - isSourceCat
is missing fields specified
in icSourceFieldsToCopy.
347 - PipelineTask form of this task
is initialized
with reference object
352 Quantities set
in exposure Metadata:
355 MAGZERO
's RMS == sigma reported by photoCal task
357 Number of stars used == ngood reported by photoCal task
366 CalibrateTask has a debug dictionary containing one key:
369 frame (an int; <= 0 to not display)
in which to display the exposure,
370 sources
and matches. See
@ref lsst.meas.astrom.displayAstrometry
for
371 the meaning of the various symbols.
374 ConfigClass = CalibrateConfig
375 _DefaultName = "calibrate"
377 def __init__(self, astromRefObjLoader=None,
378 photoRefObjLoader=None, icSourceSchema=None,
379 initInputs=None, **kwargs):
380 super().__init__(**kwargs)
382 if initInputs
is not None:
383 icSourceSchema = initInputs[
'icSourceSchema'].schema
385 if icSourceSchema
is not None:
388 minimumSchema = afwTable.SourceTable.makeMinimalSchema()
389 self.
schemaMapper.addMinimalSchema(minimumSchema,
False)
398 "Source was detected as an icSource"))
399 missingFieldNames = []
400 for fieldName
in self.config.icSourceFieldsToCopy:
402 schemaItem = icSourceSchema.find(fieldName)
404 missingFieldNames.append(fieldName)
409 if missingFieldNames:
410 raise RuntimeError(
"isSourceCat is missing fields {} "
411 "specified in icSourceFieldsToCopy"
412 .format(missingFieldNames))
419 self.
schema = afwTable.SourceTable.makeMinimalSchema()
420 self.makeSubtask(
'detection', schema=self.
schema)
424 if self.config.doDeblend:
425 self.makeSubtask(
"deblend", schema=self.
schema)
426 if self.config.doSkySources:
427 self.makeSubtask(
"skySources")
429 self.makeSubtask(
'measurement', schema=self.
schema,
431 self.makeSubtask(
'postCalibrationMeasurement', schema=self.
schema,
433 self.makeSubtask(
"setPrimaryFlags", schema=self.
schema, isSingleFrame=
True)
434 if self.config.doApCorr:
435 self.makeSubtask(
'applyApCorr', schema=self.
schema)
436 self.makeSubtask(
'catalogCalculation', schema=self.
schema)
438 if self.config.doAstrometry:
439 self.makeSubtask(
"astrometry", refObjLoader=astromRefObjLoader,
441 if self.config.doPhotoCal:
442 self.makeSubtask(
"photoCal", refObjLoader=photoRefObjLoader,
444 if self.config.doComputeSummaryStats:
445 self.makeSubtask(
'computeSummaryStats')
447 if initInputs
is not None and (astromRefObjLoader
is not None or photoRefObjLoader
is not None):
448 raise RuntimeError(
"PipelineTask form of this task should not be initialized with "
449 "reference object loaders.")
454 self.
schema.checkUnits(parse_strict=self.config.checkUnitsParseStrict)
461 inputs = butlerQC.get(inputRefs)
462 inputs[
'idGenerator'] = self.config.idGenerator.apply(butlerQC.quantum.dataId)
464 if self.config.doAstrometry:
466 for ref
in inputRefs.astromRefCat],
467 refCats=inputs.pop(
'astromRefCat'),
468 name=self.config.connections.astromRefCat,
469 config=self.config.astromRefObjLoader, log=self.log)
470 self.astrometry.setRefObjLoader(refObjLoader)
472 if self.config.doPhotoCal:
474 for ref
in inputRefs.photoRefCat],
475 refCats=inputs.pop(
'photoRefCat'),
476 name=self.config.connections.photoRefCat,
477 config=self.config.photoRefObjLoader,
479 self.photoCal.match.setRefObjLoader(photoRefObjLoader)
481 outputs = self.
run(**inputs)
483 if self.config.doWriteMatches
and self.config.doAstrometry:
484 if outputs.astromMatches
is not None:
486 normalizedMatches.table.setMetadata(outputs.matchMeta)
487 if self.config.doWriteMatchesDenormalized:
488 denormMatches = denormalizeMatches(outputs.astromMatches, outputs.matchMeta)
489 outputs.matchesDenormalized = denormMatches
490 outputs.matches = normalizedMatches
492 del outputRefs.matches
493 if self.config.doWriteMatchesDenormalized:
494 del outputRefs.matchesDenormalized
495 butlerQC.put(outputs, outputRefs)
498 def run(self, exposure, exposureIdInfo=None, background=None,
499 icSourceCat=None, idGenerator=None):
500 """Calibrate an exposure.
504 exposure : `lsst.afw.image.ExposureF`
505 Exposure to calibrate.
506 exposureIdInfo : `lsst.obs.baseExposureIdInfo`, optional
507 Exposure ID info. Deprecated in favor of ``idGenerator``,
and
508 ignored
if that
is provided.
510 Initial model of background already subtracted
from exposure.
511 icSourceCat : `lsst.afw.image.SourceCatalog`, optional
512 SourceCatalog
from CharacterizeImageTask
from which we can copy
515 Object that generates source IDs
and provides RNG seeds.
519 result : `lsst.pipe.base.Struct`
520 Results
as a struct
with attributes:
523 Characterized exposure (`lsst.afw.image.ExposureF`).
529 List of source/ref matches
from astrometry solver.
531 Metadata
from astrometry matches.
533 Another reference to ``exposure``
for compatibility.
535 Another reference to ``sourceCat``
for compatibility.
538 if idGenerator
is None:
539 if exposureIdInfo
is not None:
540 idGenerator = IdGenerator._from_exposure_id_info(exposureIdInfo)
544 if background
is None:
546 table = SourceTable.make(self.
schema, idGenerator.make_table_id_factory())
549 detRes = self.detection.run(table=table, exposure=exposure,
551 sourceCat = detRes.sources
552 if detRes.background:
553 for bg
in detRes.background:
554 background.append(bg)
555 if self.config.doSkySources:
556 skySourceFootprints = self.skySources.run(mask=exposure.mask, seed=idGenerator.catalog_id)
557 if skySourceFootprints:
558 for foot
in skySourceFootprints:
559 s = sourceCat.addNew()
562 if self.config.doDeblend:
563 self.deblend.run(exposure=exposure, sources=sourceCat)
564 self.measurement.run(
567 exposureId=idGenerator.catalog_id,
569 if self.config.doApCorr:
570 apCorrMap = exposure.getInfo().getApCorrMap()
571 if apCorrMap
is None:
572 self.log.warning(
"Image does not have valid aperture correction map for %r; "
573 "skipping aperture correction", idGenerator)
575 self.applyApCorr.run(
579 self.catalogCalculation.run(sourceCat)
581 self.setPrimaryFlags.run(sourceCat)
583 if icSourceCat
is not None and \
584 len(self.config.icSourceFieldsToCopy) > 0:
592 if not sourceCat.isContiguous():
593 sourceCat = sourceCat.copy(deep=
True)
599 if self.config.doAstrometry:
600 astromRes = self.astrometry.run(
604 astromMatches = astromRes.matches
605 matchMeta = astromRes.matchMeta
606 if exposure.getWcs()
is None:
607 if self.config.requireAstrometry:
608 raise RuntimeError(f
"WCS fit failed for {idGenerator} and requireAstrometry "
611 self.log.warning(
"Unable to perform astrometric calibration for %r but "
612 "requireAstrometry is False: attempting to proceed...",
616 if self.config.doPhotoCal:
617 if np.all(np.isnan(sourceCat[
"coord_ra"]))
or np.all(np.isnan(sourceCat[
"coord_dec"])):
618 if self.config.requirePhotoCal:
619 raise RuntimeError(f
"Astrometry failed for {idGenerator}, so cannot do "
620 "photoCal, but requirePhotoCal is True.")
621 self.log.warning(
"Astrometry failed for %r, so cannot do photoCal. requirePhotoCal "
622 "is False, so skipping photometric calibration and setting photoCalib "
623 "to None. Attempting to proceed...", idGenerator)
624 exposure.setPhotoCalib(
None)
628 photoRes = self.photoCal.run(
629 exposure, sourceCat=sourceCat, expId=idGenerator.catalog_id
631 exposure.setPhotoCalib(photoRes.photoCalib)
634 self.log.info(
"Photometric zero-point: %f",
635 photoRes.photoCalib.instFluxToMagnitude(1.0))
636 self.
setMetadata(exposure=exposure, photoRes=photoRes)
637 except Exception
as e:
638 if self.config.requirePhotoCal:
640 self.log.warning(
"Unable to perform photometric calibration "
641 "(%s): attempting to proceed", e)
644 self.postCalibrationMeasurement.run(
647 exposureId=idGenerator.catalog_id,
650 if self.config.doComputeSummaryStats:
651 summary = self.computeSummaryStats.run(exposure=exposure,
653 background=background)
654 exposure.getInfo().setSummaryStats(summary)
656 frame = getDebugFrame(self._display,
"calibrate")
661 matches=astromMatches,
666 return pipeBase.Struct(
668 astromMatches=astromMatches,
670 outputExposure=exposure,
672 outputBackground=background,
676 """Set task and exposure metadata.
678 Logs a warning continues if needed data
is missing.
682 exposure : `lsst.afw.image.ExposureF`
683 Exposure to set metadata on.
684 photoRes : `lsst.pipe.base.Struct`, optional
685 Result of running photoCal task.
690 metadata = exposure.getMetadata()
694 exposureTime = exposure.getInfo().getVisitInfo().getExposureTime()
695 magZero = photoRes.zp - 2.5*math.log10(exposureTime)
697 self.log.warning(
"Could not set normalized MAGZERO in header: no "
702 metadata.set(
'MAGZERO', magZero)
703 metadata.set(
'MAGZERO_RMS', photoRes.sigma)
704 metadata.set(
'MAGZERO_NOBJ', photoRes.ngood)
705 metadata.set(
'COLORTERM1', 0.0)
706 metadata.set(
'COLORTERM2', 0.0)
707 metadata.set(
'COLORTERM3', 0.0)
708 except Exception
as e:
709 self.log.warning(
"Could not set exposure metadata: %s", e)
712 """Match sources in an icSourceCat and a sourceCat and copy fields.
714 The fields copied are those specified by
715 ``config.icSourceFieldsToCopy``.
720 Catalog from which to copy fields.
722 Catalog to which to copy fields.
727 Raised
if any of the following occur:
728 - icSourceSchema
and icSourceKeys are
not specified.
729 - icSourceCat
and sourceCat are
not specified.
730 - icSourceFieldsToCopy
is empty.
733 raise RuntimeError(
"To copy icSource fields you must specify "
734 "icSourceSchema and icSourceKeys when "
735 "constructing this task")
736 if icSourceCat
is None or sourceCat
is None:
737 raise RuntimeError(
"icSourceCat and sourceCat must both be "
739 if len(self.config.icSourceFieldsToCopy) == 0:
740 self.log.warning(
"copyIcSourceFields doing nothing because "
741 "icSourceFieldsToCopy is empty")
745 mc.findOnlyClosest =
False
747 self.config.matchRadiusPix, mc)
748 if self.config.doDeblend:
749 deblendKey = sourceCat.schema[
"deblend_nChild"].asKey()
751 matches = [m
for m
in matches
if m[1].get(deblendKey) == 0]
758 for m0, m1, d
in matches:
760 match = bestMatches.get(id0)
761 if match
is None or d <= match[2]:
762 bestMatches[id0] = (m0, m1, d)
763 matches =
list(bestMatches.values())
768 numMatches = len(matches)
769 numUniqueSources = len(
set(m[1].getId()
for m
in matches))
770 if numUniqueSources != numMatches:
771 self.log.warning(
"%d icSourceCat sources matched only %d sourceCat "
772 "sources", numMatches, numUniqueSources)
774 self.log.info(
"Copying flags from icSourceCat to sourceCat for "
775 "%d sources", numMatches)
779 for icSrc, src, d
in matches:
785 icSrcFootprint = icSrc.getFootprint()
787 icSrc.setFootprint(src.getFootprint())
790 icSrc.setFootprint(icSrcFootprint)
afw::table::PointKey< int > dimensions
Pass parameters to algorithms that match list of sources.
Defines the fields and offsets for a table.
A mapping between the keys of two Schemas, used to copy data between them.
Class for storing ordered metadata with comments.
postCalibrationMeasurement
__init__(self, *config=None)
run(self, exposure, exposureIdInfo=None, background=None, icSourceCat=None, idGenerator=None)
runQuantum(self, butlerQC, inputRefs, outputRefs)
copyIcSourceFields(self, icSourceCat, sourceCat)
setMetadata(self, exposure, photoRes=None)
daf::base::PropertyList * list
daf::base::PropertySet * set
BaseCatalog packMatches(std::vector< Match< Record1, Record2 > > const &matches)
Return a table representation of a MatchVector that can be used to persist it.
SourceMatchVector matchXy(SourceCatalog const &cat1, SourceCatalog const &cat2, double radius, MatchControl const &mc=MatchControl())
Compute all tuples (s1,s2,d) where s1 belings to cat1, s2 belongs to cat2 and d, the distance between...
A description of a field in a table.