434 photoRefObjLoader=None, icSourceSchema=None,
435 initInputs=None, **kwargs):
438 if initInputs
is not None:
439 icSourceSchema = initInputs[
'icSourceSchema'].schema
441 if icSourceSchema
is not None:
444 minimumSchema = afwTable.SourceTable.makeMinimalSchema()
445 self.
schemaMapper.addMinimalSchema(minimumSchema,
False)
454 "Source was detected as an icSource"))
455 missingFieldNames = []
456 for fieldName
in self.config.icSourceFieldsToCopy:
458 schemaItem = icSourceSchema.find(fieldName)
460 missingFieldNames.append(fieldName)
465 if missingFieldNames:
466 raise RuntimeError(
"isSourceCat is missing fields {} "
467 "specified in icSourceFieldsToCopy"
468 .format(missingFieldNames))
475 self.
schema = afwTable.SourceTable.makeMinimalSchema()
476 afwTable.CoordKey.addErrorFields(self.
schema)
477 self.makeSubtask(
'detection', schema=self.
schema)
481 if self.config.doDeblend:
482 self.makeSubtask(
"deblend", schema=self.
schema)
483 if self.config.doSkySources:
484 self.makeSubtask(
"skySources")
486 self.makeSubtask(
'measurement', schema=self.
schema,
488 if self.config.doNormalizedCalibration:
489 self.makeSubtask(
'normalizedCalibrationFlux', schema=self.
schema)
490 self.makeSubtask(
'postCalibrationMeasurement', schema=self.
schema,
492 self.makeSubtask(
"setPrimaryFlags", schema=self.
schema, isSingleFrame=
True)
493 if self.config.doApCorr:
494 self.makeSubtask(
'applyApCorr', schema=self.
schema)
495 self.makeSubtask(
'catalogCalculation', schema=self.
schema)
497 if self.config.doAstrometry:
498 self.makeSubtask(
"astrometry", refObjLoader=astromRefObjLoader,
500 if self.config.doPhotoCal:
501 self.makeSubtask(
"photoCal", refObjLoader=photoRefObjLoader,
503 if self.config.doComputeSummaryStats:
504 self.makeSubtask(
'computeSummaryStats')
505 if self.config.doCreateSummaryMetrics:
506 self.makeSubtask(
'createSummaryMetrics')
508 if initInputs
is not None and (astromRefObjLoader
is not None or photoRefObjLoader
is not None):
509 raise RuntimeError(
"PipelineTask form of this task should not be initialized with "
510 "reference object loaders.")
515 self.
schema.checkUnits(parse_strict=self.config.checkUnitsParseStrict)
522 inputs = butlerQC.get(inputRefs)
523 inputs[
'idGenerator'] = self.config.idGenerator.apply(butlerQC.quantum.dataId)
525 if self.config.doAstrometry:
527 for ref
in inputRefs.astromRefCat],
528 refCats=inputs.pop(
'astromRefCat'),
529 name=self.config.connections.astromRefCat,
530 config=self.config.astromRefObjLoader, log=self.log)
531 self.astrometry.setRefObjLoader(refObjLoader)
533 if self.config.doPhotoCal:
535 for ref
in inputRefs.photoRefCat],
536 refCats=inputs.pop(
'photoRefCat'),
537 name=self.config.connections.photoRefCat,
538 config=self.config.photoRefObjLoader,
540 self.photoCal.match.setRefObjLoader(photoRefObjLoader)
542 outputs = self.
run(**inputs)
544 if self.config.doWriteMatches
and self.config.doAstrometry:
545 if outputs.astromMatches
is not None:
547 normalizedMatches.table.setMetadata(outputs.matchMeta)
548 if self.config.doWriteMatchesDenormalized:
549 denormMatches = denormalizeMatches(outputs.astromMatches, outputs.matchMeta)
550 outputs.matchesDenormalized = denormMatches
551 outputs.matches = normalizedMatches
553 del outputRefs.matches
554 if self.config.doWriteMatchesDenormalized:
555 del outputRefs.matchesDenormalized
556 butlerQC.put(outputs, outputRefs)
559 def run(self, exposure, background=None,
560 icSourceCat=None, idGenerator=None):
561 """Calibrate an exposure.
565 exposure : `lsst.afw.image.ExposureF`
566 Exposure to calibrate.
567 background : `lsst.afw.math.BackgroundList`, optional
568 Initial model of background already subtracted from exposure.
569 icSourceCat : `lsst.afw.image.SourceCatalog`, optional
570 SourceCatalog from CharacterizeImageTask from which we can copy
572 idGenerator : `lsst.meas.base.IdGenerator`, optional
573 Object that generates source IDs and provides RNG seeds.
577 result : `lsst.pipe.base.Struct`
578 Results as a struct with attributes:
581 Characterized exposure (`lsst.afw.image.ExposureF`).
583 Detected sources (`lsst.afw.table.SourceCatalog`).
585 Model of subtracted background (`lsst.afw.math.BackgroundList`).
587 List of source/ref matches from astrometry solver.
589 Metadata from astrometry matches.
591 Another reference to ``exposure`` for compatibility.
593 Another reference to ``sourceCat`` for compatibility.
596 if idGenerator
is None:
599 if background
is None:
601 table = SourceTable.make(self.
schema, idGenerator.make_table_id_factory())
604 detRes = self.detection.run(table=table, exposure=exposure,
608 self.metadata[
'positive_footprint_count'] = detRes.numPos
609 self.metadata[
'negative_footprint_count'] = detRes.numNeg
611 sourceCat = detRes.sources
612 if detRes.background:
613 for bg
in detRes.background:
614 background.append(bg)
615 if self.config.doSkySources:
616 skySourceFootprints = self.skySources.run(mask=exposure.mask, seed=idGenerator.catalog_id)
617 if skySourceFootprints:
618 self.metadata[
'sky_footprint_count'] = len(skySourceFootprints)
619 for foot
in skySourceFootprints:
620 s = sourceCat.addNew()
623 if self.config.doDeblend:
624 self.deblend.run(exposure=exposure, sources=sourceCat)
625 if not sourceCat.isContiguous():
626 sourceCat = sourceCat.copy(deep=
True)
627 self.metadata[
'source_count'] = len(sourceCat)
628 self.measurement.run(
631 exposureId=idGenerator.catalog_id,
633 self.metadata[
'saturated_source_count'] = (
634 np.sum(sourceCat[
'base_PixelFlags_flag_saturated'])
636 self.metadata[
'bad_source_count'] = (
637 np.sum(sourceCat[
'base_PixelFlags_flag_bad'])
639 if self.config.doNormalizedCalibration:
640 self.normalizedCalibrationFlux.run(
644 if self.config.doApCorr:
645 apCorrMap = exposure.getInfo().getApCorrMap()
646 if apCorrMap
is None:
647 self.log.warning(
"Image does not have valid aperture correction map for %r; "
648 "skipping aperture correction", idGenerator.catalog_id)
650 self.applyApCorr.run(
654 self.catalogCalculation.run(sourceCat)
656 self.setPrimaryFlags.run(sourceCat)
658 if icSourceCat
is not None and \
659 len(self.config.icSourceFieldsToCopy) > 0:
667 if not sourceCat.isContiguous():
668 sourceCat = sourceCat.copy(deep=
True)
674 if self.config.doAstrometry:
676 astromRes = self.astrometry.run(exposure=exposure, sourceCat=sourceCat)
677 astromMatches = astromRes.matches
678 matchMeta = astromRes.matchMeta
679 except AstrometryError
as e:
682 if exposure.getWcs()
is None:
683 if self.config.requireAstrometry:
684 raise RuntimeError(f
"WCS fit failed for {idGenerator} and requireAstrometry "
687 self.log.warning(
"Unable to perform astrometric calibration for %r but "
688 "requireAstrometry is False: attempting to proceed...",
692 if self.config.doPhotoCal:
693 if np.all(np.isnan(sourceCat[
"coord_ra"]))
or np.all(np.isnan(sourceCat[
"coord_dec"])):
694 if self.config.requirePhotoCal:
695 raise RuntimeError(f
"Astrometry failed for {idGenerator}, so cannot do "
696 "photoCal, but requirePhotoCal is True.")
697 self.log.warning(
"Astrometry failed for %r, so cannot do photoCal. requirePhotoCal "
698 "is False, so skipping photometric calibration and setting photoCalib "
699 "to None. Attempting to proceed...", idGenerator)
700 exposure.setPhotoCalib(
None)
704 photoRes = self.photoCal.run(
705 exposure, sourceCat=sourceCat, expId=idGenerator.catalog_id
707 exposure.setPhotoCalib(photoRes.photoCalib)
710 self.log.info(
"Photometric zero-point: %f",
711 photoRes.photoCalib.instFluxToMagnitude(1.0))
712 self.
setMetadata(exposure=exposure, photoRes=photoRes)
713 except Exception
as e:
714 if self.config.requirePhotoCal:
716 self.log.warning(
"Unable to perform photometric calibration "
717 "(%s): attempting to proceed", e)
720 self.postCalibrationMeasurement.run(
723 exposureId=idGenerator.catalog_id,
726 summaryMetrics =
None
727 if self.config.doComputeSummaryStats:
728 summary = self.computeSummaryStats.run(exposure=exposure,
730 background=background)
731 exposure.getInfo().setSummaryStats(summary)
732 if self.config.doCreateSummaryMetrics:
733 summaryMetrics = self.createSummaryMetrics.run(data=summary.__dict__).metrics
735 frame = getDebugFrame(self._display,
"calibrate")
740 matches=astromMatches,
745 return pipeBase.Struct(
747 astromMatches=astromMatches,
749 outputExposure=exposure,
751 outputBackground=background,
752 outputSummaryMetrics=summaryMetrics
792 """Match sources in an icSourceCat and a sourceCat and copy fields.
794 The fields copied are those specified by
795 ``config.icSourceFieldsToCopy``.
799 icSourceCat : `lsst.afw.table.SourceCatalog`
800 Catalog from which to copy fields.
801 sourceCat : `lsst.afw.table.SourceCatalog`
802 Catalog to which to copy fields.
807 Raised if any of the following occur:
808 - icSourceSchema and icSourceKeys are not specified.
809 - icSourceCat and sourceCat are not specified.
810 - icSourceFieldsToCopy is empty.
813 raise RuntimeError(
"To copy icSource fields you must specify "
814 "icSourceSchema and icSourceKeys when "
815 "constructing this task")
816 if icSourceCat
is None or sourceCat
is None:
817 raise RuntimeError(
"icSourceCat and sourceCat must both be "
819 if len(self.config.icSourceFieldsToCopy) == 0:
820 self.log.warning(
"copyIcSourceFields doing nothing because "
821 "icSourceFieldsToCopy is empty")
825 mc.findOnlyClosest =
False
827 self.config.matchRadiusPix, mc)
828 if self.config.doDeblend:
829 deblendKey = sourceCat.schema[
"deblend_nChild"].asKey()
831 matches = [m
for m
in matches
if m[1].get(deblendKey) == 0]
838 for m0, m1, d
in matches:
840 match = bestMatches.get(id0)
841 if match
is None or d <= match[2]:
842 bestMatches[id0] = (m0, m1, d)
843 matches = list(bestMatches.values())
848 numMatches = len(matches)
849 numUniqueSources = len(set(m[1].getId()
for m
in matches))
850 if numUniqueSources != numMatches:
851 self.log.warning(
"%d icSourceCat sources matched only %d sourceCat "
852 "sources", numMatches, numUniqueSources)
854 self.log.info(
"Copying flags from icSourceCat to sourceCat for "
855 "%d sources", numMatches)
859 for icSrc, src, d
in matches:
865 icSrcFootprint = icSrc.getFootprint()
867 icSrc.setFootprint(src.getFootprint())
870 icSrc.setFootprint(icSrcFootprint)