380 photoRefObjLoader=None, icSourceSchema=None,
381 initInputs=None, **kwargs):
384 if initInputs
is not None:
385 icSourceSchema = initInputs[
'icSourceSchema'].schema
387 if icSourceSchema
is not None:
390 minimumSchema = afwTable.SourceTable.makeMinimalSchema()
391 self.
schemaMapper.addMinimalSchema(minimumSchema,
False)
399 afwTable.Field[
"Flag"](
"calib_detected",
400 "Source was detected as an icSource"))
401 missingFieldNames = []
402 for fieldName
in self.config.icSourceFieldsToCopy:
404 schemaItem = icSourceSchema.find(fieldName)
406 missingFieldNames.append(fieldName)
411 if missingFieldNames:
412 raise RuntimeError(
"isSourceCat is missing fields {} "
413 "specified in icSourceFieldsToCopy"
414 .format(missingFieldNames))
421 self.
schema = afwTable.SourceTable.makeMinimalSchema()
422 afwTable.CoordKey.addErrorFields(self.
schema)
423 self.makeSubtask(
'detection', schema=self.
schema)
427 if self.config.doDeblend:
428 self.makeSubtask(
"deblend", schema=self.
schema)
429 if self.config.doSkySources:
430 self.makeSubtask(
"skySources")
432 self.makeSubtask(
'measurement', schema=self.
schema,
434 self.makeSubtask(
'postCalibrationMeasurement', schema=self.
schema,
436 self.makeSubtask(
"setPrimaryFlags", schema=self.
schema, isSingleFrame=
True)
437 if self.config.doApCorr:
438 self.makeSubtask(
'applyApCorr', schema=self.
schema)
439 self.makeSubtask(
'catalogCalculation', schema=self.
schema)
441 if self.config.doAstrometry:
442 self.makeSubtask(
"astrometry", refObjLoader=astromRefObjLoader,
444 if self.config.doPhotoCal:
445 self.makeSubtask(
"photoCal", refObjLoader=photoRefObjLoader,
447 if self.config.doComputeSummaryStats:
448 self.makeSubtask(
'computeSummaryStats')
450 if initInputs
is not None and (astromRefObjLoader
is not None or photoRefObjLoader
is not None):
451 raise RuntimeError(
"PipelineTask form of this task should not be initialized with "
452 "reference object loaders.")
457 self.
schema.checkUnits(parse_strict=self.config.checkUnitsParseStrict)
459 sourceCatSchema = afwTable.SourceCatalog(self.
schema)
464 inputs = butlerQC.get(inputRefs)
465 inputs[
'idGenerator'] = self.config.idGenerator.apply(butlerQC.quantum.dataId)
467 if self.config.doAstrometry:
468 refObjLoader = ReferenceObjectLoader(dataIds=[ref.datasetRef.dataId
469 for ref
in inputRefs.astromRefCat],
470 refCats=inputs.pop(
'astromRefCat'),
471 name=self.config.connections.astromRefCat,
472 config=self.config.astromRefObjLoader, log=self.log)
473 self.astrometry.setRefObjLoader(refObjLoader)
475 if self.config.doPhotoCal:
476 photoRefObjLoader = ReferenceObjectLoader(dataIds=[ref.datasetRef.dataId
477 for ref
in inputRefs.photoRefCat],
478 refCats=inputs.pop(
'photoRefCat'),
479 name=self.config.connections.photoRefCat,
480 config=self.config.photoRefObjLoader,
482 self.photoCal.match.setRefObjLoader(photoRefObjLoader)
484 outputs = self.
run(**inputs)
486 if self.config.doWriteMatches
and self.config.doAstrometry:
487 if outputs.astromMatches
is not None:
488 normalizedMatches = afwTable.packMatches(outputs.astromMatches)
489 normalizedMatches.table.setMetadata(outputs.matchMeta)
490 if self.config.doWriteMatchesDenormalized:
491 denormMatches = denormalizeMatches(outputs.astromMatches, outputs.matchMeta)
492 outputs.matchesDenormalized = denormMatches
493 outputs.matches = normalizedMatches
495 del outputRefs.matches
496 if self.config.doWriteMatchesDenormalized:
497 del outputRefs.matchesDenormalized
498 butlerQC.put(outputs, outputRefs)
501 def run(self, exposure, exposureIdInfo=None, background=None,
502 icSourceCat=None, idGenerator=None):
503 """Calibrate an exposure.
507 exposure : `lsst.afw.image.ExposureF`
508 Exposure to calibrate.
509 exposureIdInfo : `lsst.obs.baseExposureIdInfo`, optional
510 Exposure ID info. Deprecated in favor of ``idGenerator``, and
511 ignored if that is provided.
512 background : `lsst.afw.math.BackgroundList`, optional
513 Initial model of background already subtracted from exposure.
514 icSourceCat : `lsst.afw.image.SourceCatalog`, optional
515 SourceCatalog from CharacterizeImageTask from which we can copy
517 idGenerator : `lsst.meas.base.IdGenerator`, optional
518 Object that generates source IDs and provides RNG seeds.
522 result : `lsst.pipe.base.Struct`
523 Results as a struct with attributes:
526 Characterized exposure (`lsst.afw.image.ExposureF`).
528 Detected sources (`lsst.afw.table.SourceCatalog`).
530 Model of subtracted background (`lsst.afw.math.BackgroundList`).
532 List of source/ref matches from astrometry solver.
534 Metadata from astrometry matches.
536 Another reference to ``exposure`` for compatibility.
538 Another reference to ``sourceCat`` for compatibility.
541 if idGenerator
is None:
542 if exposureIdInfo
is not None:
543 idGenerator = IdGenerator._from_exposure_id_info(exposureIdInfo)
545 idGenerator = IdGenerator()
547 if background
is None:
548 background = BackgroundList()
549 table = SourceTable.make(self.
schema, idGenerator.make_table_id_factory())
552 detRes = self.detection.run(table=table, exposure=exposure,
554 sourceCat = detRes.sources
555 if detRes.background:
556 for bg
in detRes.background:
557 background.append(bg)
558 if self.config.doSkySources:
559 skySourceFootprints = self.skySources.run(mask=exposure.mask, seed=idGenerator.catalog_id)
560 if skySourceFootprints:
561 for foot
in skySourceFootprints:
562 s = sourceCat.addNew()
565 if self.config.doDeblend:
566 self.deblend.run(exposure=exposure, sources=sourceCat)
567 self.measurement.run(
570 exposureId=idGenerator.catalog_id,
572 if self.config.doApCorr:
573 apCorrMap = exposure.getInfo().getApCorrMap()
574 if apCorrMap
is None:
575 self.log.warning(
"Image does not have valid aperture correction map for %r; "
576 "skipping aperture correction", idGenerator)
578 self.applyApCorr.run(
582 self.catalogCalculation.run(sourceCat)
584 self.setPrimaryFlags.run(sourceCat)
586 if icSourceCat
is not None and \
587 len(self.config.icSourceFieldsToCopy) > 0:
595 if not sourceCat.isContiguous():
596 sourceCat = sourceCat.copy(deep=
True)
602 if self.config.doAstrometry:
603 astromRes = self.astrometry.run(
607 astromMatches = astromRes.matches
608 matchMeta = astromRes.matchMeta
609 if exposure.getWcs()
is None:
610 if self.config.requireAstrometry:
611 raise RuntimeError(f
"WCS fit failed for {idGenerator} and requireAstrometry "
614 self.log.warning(
"Unable to perform astrometric calibration for %r but "
615 "requireAstrometry is False: attempting to proceed...",
619 if self.config.doPhotoCal:
620 if np.all(np.isnan(sourceCat[
"coord_ra"]))
or np.all(np.isnan(sourceCat[
"coord_dec"])):
621 if self.config.requirePhotoCal:
622 raise RuntimeError(f
"Astrometry failed for {idGenerator}, so cannot do "
623 "photoCal, but requirePhotoCal is True.")
624 self.log.warning(
"Astrometry failed for %r, so cannot do photoCal. requirePhotoCal "
625 "is False, so skipping photometric calibration and setting photoCalib "
626 "to None. Attempting to proceed...", idGenerator)
627 exposure.setPhotoCalib(
None)
631 photoRes = self.photoCal.run(
632 exposure, sourceCat=sourceCat, expId=idGenerator.catalog_id
634 exposure.setPhotoCalib(photoRes.photoCalib)
637 self.log.info(
"Photometric zero-point: %f",
638 photoRes.photoCalib.instFluxToMagnitude(1.0))
639 self.
setMetadata(exposure=exposure, photoRes=photoRes)
640 except Exception
as e:
641 if self.config.requirePhotoCal:
643 self.log.warning(
"Unable to perform photometric calibration "
644 "(%s): attempting to proceed", e)
647 self.postCalibrationMeasurement.run(
650 exposureId=idGenerator.catalog_id,
653 if self.config.doComputeSummaryStats:
654 summary = self.computeSummaryStats.run(exposure=exposure,
656 background=background)
657 exposure.getInfo().setSummaryStats(summary)
659 frame = getDebugFrame(self._display,
"calibrate")
664 matches=astromMatches,
669 return pipeBase.Struct(
671 astromMatches=astromMatches,
673 outputExposure=exposure,
675 outputBackground=background,
715 """Match sources in an icSourceCat and a sourceCat and copy fields.
717 The fields copied are those specified by
718 ``config.icSourceFieldsToCopy``.
722 icSourceCat : `lsst.afw.table.SourceCatalog`
723 Catalog from which to copy fields.
724 sourceCat : `lsst.afw.table.SourceCatalog`
725 Catalog to which to copy fields.
730 Raised if any of the following occur:
731 - icSourceSchema and icSourceKeys are not specified.
732 - icSourceCat and sourceCat are not specified.
733 - icSourceFieldsToCopy is empty.
736 raise RuntimeError(
"To copy icSource fields you must specify "
737 "icSourceSchema and icSourceKeys when "
738 "constructing this task")
739 if icSourceCat
is None or sourceCat
is None:
740 raise RuntimeError(
"icSourceCat and sourceCat must both be "
742 if len(self.config.icSourceFieldsToCopy) == 0:
743 self.log.warning(
"copyIcSourceFields doing nothing because "
744 "icSourceFieldsToCopy is empty")
747 mc = afwTable.MatchControl()
748 mc.findOnlyClosest =
False
749 matches = afwTable.matchXy(icSourceCat, sourceCat,
750 self.config.matchRadiusPix, mc)
751 if self.config.doDeblend:
752 deblendKey = sourceCat.schema[
"deblend_nChild"].asKey()
754 matches = [m
for m
in matches
if m[1].get(deblendKey) == 0]
761 for m0, m1, d
in matches:
763 match = bestMatches.get(id0)
764 if match
is None or d <= match[2]:
765 bestMatches[id0] = (m0, m1, d)
766 matches = list(bestMatches.values())
771 numMatches = len(matches)
772 numUniqueSources = len(set(m[1].getId()
for m
in matches))
773 if numUniqueSources != numMatches:
774 self.log.warning(
"%d icSourceCat sources matched only %d sourceCat "
775 "sources", numMatches, numUniqueSources)
777 self.log.info(
"Copying flags from icSourceCat to sourceCat for "
778 "%d sources", numMatches)
782 for icSrc, src, d
in matches:
788 icSrcFootprint = icSrc.getFootprint()
790 icSrc.setFootprint(src.getFootprint())
793 icSrc.setFootprint(icSrcFootprint)