540 icSourceCat=None, idGenerator=None):
541 """Calibrate an exposure.
542
543 Parameters
544 ----------
545 exposure : `lsst.afw.image.ExposureF`
546 Exposure to calibrate.
547 background : `lsst.afw.math.BackgroundList`, optional
548 Initial model of background already subtracted from exposure.
549 icSourceCat : `lsst.afw.image.SourceCatalog`, optional
550 SourceCatalog from CharacterizeImageTask from which we can copy
551 some fields.
552 idGenerator : `lsst.meas.base.IdGenerator`, optional
553 Object that generates source IDs and provides RNG seeds.
554
555 Returns
556 -------
557 result : `lsst.pipe.base.Struct`
558 Results as a struct with attributes:
559
560 ``exposure``
561 Characterized exposure (`lsst.afw.image.ExposureF`).
562 ``sourceCat``
563 Detected sources (`lsst.afw.table.SourceCatalog`).
564 ``outputBackground``
565 Model of subtracted background (`lsst.afw.math.BackgroundList`).
566 ``astromMatches``
567 List of source/ref matches from astrometry solver.
568 ``matchMeta``
569 Metadata from astrometry matches.
570 ``outputExposure``
571 Another reference to ``exposure`` for compatibility.
572 ``outputCat``
573 Another reference to ``sourceCat`` for compatibility.
574 """
575
576 if idGenerator is None:
577 idGenerator = IdGenerator()
578
579 if background is None:
580 background = BackgroundList()
581 table = SourceTable.make(self.schema, idGenerator.make_table_id_factory())
582 table.setMetadata(self.algMetadata)
583
584 detRes = self.detection.run(table=table, exposure=exposure,
585 doSmooth=True)
586 sourceCat = detRes.sources
587 if detRes.background:
588 for bg in detRes.background:
589 background.append(bg)
590 if self.config.doSkySources:
591 skySourceFootprints = self.skySources.run(mask=exposure.mask, seed=idGenerator.catalog_id)
592 if skySourceFootprints:
593 for foot in skySourceFootprints:
594 s = sourceCat.addNew()
595 s.setFootprint(foot)
596 s.set(self.skySourceKey, True)
597 if self.config.doDeblend:
598 self.deblend.run(exposure=exposure, sources=sourceCat)
599 if not sourceCat.isContiguous():
600 sourceCat = sourceCat.copy(deep=True)
601 self.measurement.run(
602 measCat=sourceCat,
603 exposure=exposure,
604 exposureId=idGenerator.catalog_id,
605 )
606 if self.config.doApCorr:
607 apCorrMap = exposure.getInfo().getApCorrMap()
608 if apCorrMap is None:
609 self.log.warning("Image does not have valid aperture correction map for %r; "
610 "skipping aperture correction", idGenerator)
611 else:
612 self.applyApCorr.run(
613 catalog=sourceCat,
614 apCorrMap=apCorrMap,
615 )
616 self.catalogCalculation.run(sourceCat)
617
618 self.setPrimaryFlags.run(sourceCat)
619
620 if icSourceCat is not None and \
621 len(self.config.icSourceFieldsToCopy) > 0:
622 self.copyIcSourceFields(icSourceCat=icSourceCat,
623 sourceCat=sourceCat)
624
625
626
627
628
629 if not sourceCat.isContiguous():
630 sourceCat = sourceCat.copy(deep=True)
631
632
633
634 astromMatches = None
635 matchMeta = None
636 if self.config.doAstrometry:
637 astromRes = self.astrometry.run(
638 exposure=exposure,
639 sourceCat=sourceCat,
640 )
641 astromMatches = astromRes.matches
642 matchMeta = astromRes.matchMeta
643 if exposure.getWcs() is None:
644 if self.config.requireAstrometry:
645 raise RuntimeError(f"WCS fit failed for {idGenerator} and requireAstrometry "
646 "is True.")
647 else:
648 self.log.warning("Unable to perform astrometric calibration for %r but "
649 "requireAstrometry is False: attempting to proceed...",
650 idGenerator)
651
652
653 if self.config.doPhotoCal:
654 if np.all(np.isnan(sourceCat["coord_ra"])) or np.all(np.isnan(sourceCat["coord_dec"])):
655 if self.config.requirePhotoCal:
656 raise RuntimeError(f"Astrometry failed for {idGenerator}, so cannot do "
657 "photoCal, but requirePhotoCal is True.")
658 self.log.warning("Astrometry failed for %r, so cannot do photoCal. requirePhotoCal "
659 "is False, so skipping photometric calibration and setting photoCalib "
660 "to None. Attempting to proceed...", idGenerator)
661 exposure.setPhotoCalib(None)
662 self.setMetadata(exposure=exposure, photoRes=None)
663 else:
664 try:
665 photoRes = self.photoCal.run(
666 exposure, sourceCat=sourceCat, expId=idGenerator.catalog_id
667 )
668 exposure.setPhotoCalib(photoRes.photoCalib)
669
670
671 self.log.info("Photometric zero-point: %f",
672 photoRes.photoCalib.instFluxToMagnitude(1.0))
673 self.setMetadata(exposure=exposure, photoRes=photoRes)
674 except Exception as e:
675 if self.config.requirePhotoCal:
676 raise
677 self.log.warning("Unable to perform photometric calibration "
678 "(%s): attempting to proceed", e)
679 self.setMetadata(exposure=exposure, photoRes=None)
680
681 self.postCalibrationMeasurement.run(
682 measCat=sourceCat,
683 exposure=exposure,
684 exposureId=idGenerator.catalog_id,
685 )
686
687 summaryMetrics = None
688 if self.config.doComputeSummaryStats:
689 summary = self.computeSummaryStats.run(exposure=exposure,
690 sources=sourceCat,
691 background=background)
692 exposure.getInfo().setSummaryStats(summary)
693 if self.config.doCreateSummaryMetrics:
694 summaryMetrics = self.createSummaryMetrics.run(data=summary.__dict__).metrics
695
696 frame = getDebugFrame(self._display, "calibrate")
697 if frame:
698 displayAstrometry(
699 sourceCat=sourceCat,
700 exposure=exposure,
701 matches=astromMatches,
702 frame=frame,
703 pause=False,
704 )
705
706 return pipeBase.Struct(
707 sourceCat=sourceCat,
708 astromMatches=astromMatches,
709 matchMeta=matchMeta,
710 outputExposure=exposure,
711 outputCat=sourceCat,
712 outputBackground=background,
713 outputSummaryMetrics=summaryMetrics
714 )
715