450 photoRefObjLoader=None, icSourceSchema=None,
451 initInputs=None, **kwargs):
454 if initInputs
is not None:
455 icSourceSchema = initInputs[
'icSourceSchema'].schema
457 if icSourceSchema
is not None:
460 minimumSchema = afwTable.SourceTable.makeMinimalSchema()
461 self.
schemaMapper.addMinimalSchema(minimumSchema,
False)
470 "Source was detected as an icSource"))
471 missingFieldNames = []
472 for fieldName
in self.config.icSourceFieldsToCopy:
474 schemaItem = icSourceSchema.find(fieldName)
476 missingFieldNames.append(fieldName)
481 if missingFieldNames:
482 raise RuntimeError(
"isSourceCat is missing fields {} "
483 "specified in icSourceFieldsToCopy"
484 .format(missingFieldNames))
491 self.
schema = afwTable.SourceTable.makeMinimalSchema()
492 afwTable.CoordKey.addErrorFields(self.
schema)
493 self.makeSubtask(
'detection', schema=self.
schema)
497 if self.config.doDeblend:
498 self.makeSubtask(
"deblend", schema=self.
schema)
499 if self.config.doSkySources:
500 self.makeSubtask(
"skySources")
502 self.makeSubtask(
'measurement', schema=self.
schema,
504 if self.config.doNormalizedCalibration:
505 self.makeSubtask(
'normalizedCalibrationFlux', schema=self.
schema)
506 self.makeSubtask(
'postCalibrationMeasurement', schema=self.
schema,
508 self.makeSubtask(
"setPrimaryFlags", schema=self.
schema, isSingleFrame=
True)
509 if self.config.doApCorr:
510 self.makeSubtask(
'applyApCorr', schema=self.
schema)
511 self.makeSubtask(
'catalogCalculation', schema=self.
schema)
513 if self.config.doAstrometry:
514 self.makeSubtask(
"astrometryDetection", schema=self.
schema)
515 self.makeSubtask(
"astrometry", refObjLoader=astromRefObjLoader,
517 if self.config.doPhotoCal:
518 self.makeSubtask(
"photoCal", refObjLoader=photoRefObjLoader,
520 if self.config.doComputeSummaryStats:
521 self.makeSubtask(
'computeSummaryStats')
522 if self.config.doCreateSummaryMetrics:
523 self.makeSubtask(
'createSummaryMetrics')
525 if initInputs
is not None and (astromRefObjLoader
is not None or photoRefObjLoader
is not None):
526 raise RuntimeError(
"PipelineTask form of this task should not be initialized with "
527 "reference object loaders.")
532 self.
schema.checkUnits(parse_strict=self.config.checkUnitsParseStrict)
539 inputs = butlerQC.get(inputRefs)
540 inputs[
'idGenerator'] = self.config.idGenerator.apply(butlerQC.quantum.dataId)
542 if self.config.doAstrometry:
544 for ref
in inputRefs.astromRefCat],
545 refCats=inputs.pop(
'astromRefCat'),
546 name=self.config.connections.astromRefCat,
547 config=self.config.astromRefObjLoader, log=self.log)
548 self.astrometry.setRefObjLoader(refObjLoader)
550 if self.config.doPhotoCal:
552 for ref
in inputRefs.photoRefCat],
553 refCats=inputs.pop(
'photoRefCat'),
554 name=self.config.connections.photoRefCat,
555 config=self.config.photoRefObjLoader,
557 self.photoCal.match.setRefObjLoader(photoRefObjLoader)
559 outputs = self.
run(**inputs)
561 if self.config.doWriteMatches
and self.config.doAstrometry:
562 if outputs.astromMatches
is not None:
564 normalizedMatches.table.setMetadata(outputs.matchMeta)
565 if self.config.doWriteMatchesDenormalized:
566 denormMatches = denormalizeMatches(outputs.astromMatches, outputs.matchMeta)
567 outputs.matchesDenormalized = denormMatches
568 outputs.matches = normalizedMatches
570 del outputRefs.matches
571 if self.config.doWriteMatchesDenormalized:
572 del outputRefs.matchesDenormalized
573 butlerQC.put(outputs, outputRefs)
576 def run(self, exposure, background=None,
577 icSourceCat=None, idGenerator=None):
578 """Calibrate an exposure.
582 exposure : `lsst.afw.image.ExposureF`
583 Exposure to calibrate.
584 background : `lsst.afw.math.BackgroundList`, optional
585 Initial model of background already subtracted from exposure.
586 icSourceCat : `lsst.afw.image.SourceCatalog`, optional
587 SourceCatalog from CharacterizeImageTask from which we can copy
589 idGenerator : `lsst.meas.base.IdGenerator`, optional
590 Object that generates source IDs and provides RNG seeds.
594 result : `lsst.pipe.base.Struct`
595 Results as a struct with attributes:
598 Characterized exposure (`lsst.afw.image.ExposureF`).
600 Detected sources (`lsst.afw.table.SourceCatalog`).
602 Model of subtracted background (`lsst.afw.math.BackgroundList`).
604 List of source/ref matches from astrometry solver.
606 Metadata from astrometry matches.
608 Another reference to ``exposure`` for compatibility.
610 Another reference to ``sourceCat`` for compatibility.
613 if idGenerator
is None:
616 if background
is None:
618 table = SourceTable.make(self.
schema, idGenerator.make_table_id_factory())
626 if self.config.doAstrometry:
629 astromDetections = self.astrometryDetection.run(
630 table=table, exposure=exposure, background=background
632 astromCat = astromDetections.sources
633 if not astromCat.isContiguous():
634 astromCat = astromCat.copy(deep=
True)
635 self.measurement.run(
638 exposureId=idGenerator.catalog_id,
640 if self.config.doNormalizedCalibration:
641 self.normalizedCalibrationFlux.run(
645 if self.config.doApCorr:
646 apCorrMap = exposure.getInfo().getApCorrMap()
647 if apCorrMap
is None:
648 self.log.warning(
"Image does not have valid aperture correction map for %r; "
649 "skipping aperture correction", idGenerator.catalog_id)
651 self.applyApCorr.run(
655 self.catalogCalculation.run(astromCat)
657 self.setPrimaryFlags.run(astromCat)
659 if icSourceCat
is not None and \
660 len(self.config.icSourceFieldsToCopy) > 0:
662 calibType=
"icSource", schemaMapper=self.
schemaMapper, calibCat=icSourceCat,
663 sourceCat=astromCat, fieldsToCopy=self.config.icSourceFieldsToCopy
666 if not astromCat.isContiguous():
667 astromCat = astromCat.copy(deep=
True)
669 astromRes = self.astrometry.run(exposure=exposure, sourceCat=astromCat)
670 astromMatches = astromRes.matches
671 matchMeta = astromRes.matchMeta
672 self.astrometry.check(exposure, astromCat, len(astromMatches))
674 except AstrometryError
as e:
677 if exposure.getWcs()
is None:
678 if self.config.requireAstrometry:
679 raise RuntimeError(f
"WCS fit failed for {idGenerator} and requireAstrometry "
682 self.log.warning(
"Unable to perform astrometric calibration for %r but "
683 "requireAstrometry is False: attempting to proceed...",
686 detRes = self.detection.run(table=table, exposure=exposure,
690 self.metadata[
'positive_footprint_count'] = detRes.numPos
691 self.metadata[
'negative_footprint_count'] = detRes.numNeg
693 sourceCat = detRes.sources
695 if exposure.wcs
is not None:
698 if detRes.background:
699 for bg
in detRes.background:
700 background.append(bg)
701 if self.config.doSkySources:
702 skySourceFootprints = self.skySources.run(mask=exposure.mask, seed=idGenerator.catalog_id)
703 if skySourceFootprints:
704 self.metadata[
'sky_footprint_count'] = len(skySourceFootprints)
705 for foot
in skySourceFootprints:
706 s = sourceCat.addNew()
709 if self.config.doDeblend:
710 self.deblend.run(exposure=exposure, sources=sourceCat)
711 if not sourceCat.isContiguous():
712 sourceCat = sourceCat.copy(deep=
True)
713 self.metadata[
'source_count'] = len(sourceCat)
714 self.measurement.run(
717 exposureId=idGenerator.catalog_id,
719 self.metadata[
'saturated_source_count'] = (
720 np.sum(sourceCat[
'base_PixelFlags_flag_saturated'])
722 self.metadata[
'bad_source_count'] = (
723 np.sum(sourceCat[
'base_PixelFlags_flag_bad'])
725 if self.config.doNormalizedCalibration:
726 self.normalizedCalibrationFlux.run(
730 if self.config.doApCorr:
731 apCorrMap = exposure.getInfo().getApCorrMap()
732 if apCorrMap
is None:
733 self.log.warning(
"Image does not have valid aperture correction map for %r; "
734 "skipping aperture correction", idGenerator.catalog_id)
736 self.applyApCorr.run(
740 self.catalogCalculation.run(sourceCat)
742 self.setPrimaryFlags.run(sourceCat)
744 if icSourceCat
is not None and \
745 len(self.config.icSourceFieldsToCopy) > 0:
747 calibCat=icSourceCat, sourceCat=sourceCat,
748 fieldsToCopy=self.config.icSourceFieldsToCopy)
750 if astromCat
is not None and \
751 len(self.config.astromFieldsToCopy) > 0:
754 calibCat=astromCat, sourceCat=sourceCat,
755 fieldsToCopy=self.config.astromFieldsToCopy)
761 if not sourceCat.isContiguous():
762 sourceCat = sourceCat.copy(deep=
True)
764 if self.config.doPhotoCal:
765 if np.all(np.isnan(sourceCat[
"coord_ra"]))
or np.all(np.isnan(sourceCat[
"coord_dec"])):
766 if self.config.requirePhotoCal:
767 raise RuntimeError(f
"Astrometry failed for {idGenerator}, so cannot do "
768 "photoCal, but requirePhotoCal is True.")
769 self.log.warning(
"Astrometry failed for %r, so cannot do photoCal. requirePhotoCal "
770 "is False, so skipping photometric calibration and setting photoCalib "
771 "to None. Attempting to proceed...", idGenerator)
772 exposure.setPhotoCalib(
None)
776 photoRes = self.photoCal.run(
777 exposure, sourceCat=sourceCat, expId=idGenerator.catalog_id
779 exposure.setPhotoCalib(photoRes.photoCalib)
782 self.log.info(
"Photometric zero-point: %f",
783 photoRes.photoCalib.instFluxToMagnitude(1.0))
784 self.
setMetadata(exposure=exposure, photoRes=photoRes)
785 except Exception
as e:
786 if self.config.requirePhotoCal:
788 self.log.warning(
"Unable to perform photometric calibration "
789 "(%s): attempting to proceed", e)
792 self.postCalibrationMeasurement.run(
795 exposureId=idGenerator.catalog_id,
798 summaryMetrics =
None
799 if self.config.doComputeSummaryStats:
800 summary = self.computeSummaryStats.run(exposure=exposure,
802 background=background)
803 exposure.getInfo().setSummaryStats(summary)
804 if self.config.doCreateSummaryMetrics:
805 summaryMetrics = self.createSummaryMetrics.run(data=summary.__dict__).metrics
807 frame = getDebugFrame(self._display,
"calibrate")
812 matches=astromMatches,
817 return pipeBase.Struct(
819 astromMatches=astromMatches,
821 outputExposure=exposure,
823 outputBackground=background,
824 outputSummaryMetrics=summaryMetrics
864 """Match sources in a calibrationCat and a sourceCat and copy fields.
866 The fields copied are those specified by
867 ``config.icSourceFieldsToCopy`` if ``calibType`` is icSource or
868 ``config.astromFieldsToCopy`` if ``calibType`` is astrometry.
873 The type of calibration: either icSource or astrometry.
874 calibCat : `lsst.afw.table.SourceCatalog`
875 Catalog from which to copy fields.
876 sourceCat : `lsst.afw.table.SourceCatalog`
877 Catalog to which to copy fields.
882 Raised if any of the following occur:
883 - calibSchema and calibSourceKeys are not specified.
884 - calibCat and sourceCat are not specified.
885 - calibFieldsToCopy is empty.
887 if schemaMapper
is None:
888 raise RuntimeError(
"To copy %s fields you must specify its "
889 "schema and keys when constructing this task", calibType)
890 if calibCat
is None or sourceCat
is None:
891 raise RuntimeError(
"the calibCat and sourceCat must both be "
893 if len(fieldsToCopy) == 0:
894 self.log.warning(
"copyCalibSourceFields doing nothing for %s because "
895 "its FieldsToCopy is empty", calibType)
899 mc.findOnlyClosest =
False
901 self.config.matchRadiusPix, mc)
902 if self.config.doDeblend:
903 deblendKey = sourceCat.schema[
"deblend_nChild"].asKey()
905 matches = [m
for m
in matches
if m[1].get(deblendKey) == 0]
912 for m0, m1, d
in matches:
914 match = bestMatches.get(id0)
915 if match
is None or d <= match[2]:
916 bestMatches[id0] = (m0, m1, d)
917 matches = list(bestMatches.values())
922 numMatches = len(matches)
923 numUniqueSources = len(set(m[1].getId()
for m
in matches))
924 if numUniqueSources != numMatches:
925 self.log.warning(
"%d %s cat sources matched only %d sourceCat "
926 "sources", numMatches, calibType, numUniqueSources)
928 self.log.info(
"Copying %s flags from calibCat to sourceCat for "
929 "%d sources", calibType, numMatches)
933 for calibSrc, src, d
in matches:
934 if calibType ==
"icSource":
937 for field
in fieldsToCopy:
938 calibKey = sourceCat.schema[field].asKey()
939 src.setFlag(calibKey,
True)
944 calibSrcFootprint = calibSrc.getFootprint()
946 calibSrc.setFootprint(src.getFootprint())
947 src.assign(calibSrc, schemaMapper)
949 calibSrc.setFootprint(calibSrcFootprint)