376 photoRefObjLoader=None, icSourceSchema=None,
377 initInputs=None, **kwargs):
380 if initInputs
is not None:
381 icSourceSchema = initInputs[
'icSourceSchema'].schema
383 if icSourceSchema
is not None:
386 minimumSchema = afwTable.SourceTable.makeMinimalSchema()
387 self.
schemaMapper.addMinimalSchema(minimumSchema,
False)
395 afwTable.Field[
"Flag"](
"calib_detected",
396 "Source was detected as an icSource"))
397 missingFieldNames = []
398 for fieldName
in self.config.icSourceFieldsToCopy:
400 schemaItem = icSourceSchema.find(fieldName)
402 missingFieldNames.append(fieldName)
407 if missingFieldNames:
408 raise RuntimeError(
"isSourceCat is missing fields {} "
409 "specified in icSourceFieldsToCopy"
410 .format(missingFieldNames))
417 self.
schema = afwTable.SourceTable.makeMinimalSchema()
418 afwTable.CoordKey.addErrorFields(self.
schema)
419 self.makeSubtask(
'detection', schema=self.
schema)
423 if self.config.doDeblend:
424 self.makeSubtask(
"deblend", schema=self.
schema)
425 if self.config.doSkySources:
426 self.makeSubtask(
"skySources")
428 self.makeSubtask(
'measurement', schema=self.
schema,
430 self.makeSubtask(
'postCalibrationMeasurement', schema=self.
schema,
432 self.makeSubtask(
"setPrimaryFlags", schema=self.
schema, isSingleFrame=
True)
433 if self.config.doApCorr:
434 self.makeSubtask(
'applyApCorr', schema=self.
schema)
435 self.makeSubtask(
'catalogCalculation', schema=self.
schema)
437 if self.config.doAstrometry:
438 self.makeSubtask(
"astrometry", refObjLoader=astromRefObjLoader,
440 if self.config.doPhotoCal:
441 self.makeSubtask(
"photoCal", refObjLoader=photoRefObjLoader,
443 if self.config.doComputeSummaryStats:
444 self.makeSubtask(
'computeSummaryStats')
446 if initInputs
is not None and (astromRefObjLoader
is not None or photoRefObjLoader
is not None):
447 raise RuntimeError(
"PipelineTask form of this task should not be initialized with "
448 "reference object loaders.")
453 self.
schema.checkUnits(parse_strict=self.config.checkUnitsParseStrict)
455 sourceCatSchema = afwTable.SourceCatalog(self.
schema)
460 inputs = butlerQC.get(inputRefs)
461 inputs[
'idGenerator'] = self.config.idGenerator.apply(butlerQC.quantum.dataId)
463 if self.config.doAstrometry:
464 refObjLoader = ReferenceObjectLoader(dataIds=[ref.datasetRef.dataId
465 for ref
in inputRefs.astromRefCat],
466 refCats=inputs.pop(
'astromRefCat'),
467 name=self.config.connections.astromRefCat,
468 config=self.config.astromRefObjLoader, log=self.log)
469 self.astrometry.setRefObjLoader(refObjLoader)
471 if self.config.doPhotoCal:
472 photoRefObjLoader = ReferenceObjectLoader(dataIds=[ref.datasetRef.dataId
473 for ref
in inputRefs.photoRefCat],
474 refCats=inputs.pop(
'photoRefCat'),
475 name=self.config.connections.photoRefCat,
476 config=self.config.photoRefObjLoader,
478 self.photoCal.match.setRefObjLoader(photoRefObjLoader)
480 outputs = self.
run(**inputs)
482 if self.config.doWriteMatches
and self.config.doAstrometry:
483 if outputs.astromMatches
is not None:
484 normalizedMatches = afwTable.packMatches(outputs.astromMatches)
485 normalizedMatches.table.setMetadata(outputs.matchMeta)
486 if self.config.doWriteMatchesDenormalized:
487 denormMatches = denormalizeMatches(outputs.astromMatches, outputs.matchMeta)
488 outputs.matchesDenormalized = denormMatches
489 outputs.matches = normalizedMatches
491 del outputRefs.matches
492 if self.config.doWriteMatchesDenormalized:
493 del outputRefs.matchesDenormalized
494 butlerQC.put(outputs, outputRefs)
497 def run(self, exposure, exposureIdInfo=None, background=None,
498 icSourceCat=None, idGenerator=None):
499 """Calibrate an exposure.
503 exposure : `lsst.afw.image.ExposureF`
504 Exposure to calibrate.
505 exposureIdInfo : `lsst.obs.baseExposureIdInfo`, optional
506 Exposure ID info. Deprecated in favor of ``idGenerator``, and
507 ignored if that is provided.
508 background : `lsst.afw.math.BackgroundList`, optional
509 Initial model of background already subtracted from exposure.
510 icSourceCat : `lsst.afw.image.SourceCatalog`, optional
511 SourceCatalog from CharacterizeImageTask from which we can copy
513 idGenerator : `lsst.meas.base.IdGenerator`, optional
514 Object that generates source IDs and provides RNG seeds.
518 result : `lsst.pipe.base.Struct`
519 Results as a struct with attributes:
522 Characterized exposure (`lsst.afw.image.ExposureF`).
524 Detected sources (`lsst.afw.table.SourceCatalog`).
526 Model of subtracted background (`lsst.afw.math.BackgroundList`).
528 List of source/ref matches from astrometry solver.
530 Metadata from astrometry matches.
532 Another reference to ``exposure`` for compatibility.
534 Another reference to ``sourceCat`` for compatibility.
537 if idGenerator
is None:
538 if exposureIdInfo
is not None:
539 idGenerator = IdGenerator._from_exposure_id_info(exposureIdInfo)
541 idGenerator = IdGenerator()
543 if background
is None:
544 background = BackgroundList()
545 table = SourceTable.make(self.
schema, idGenerator.make_table_id_factory())
548 detRes = self.detection.run(table=table, exposure=exposure,
550 sourceCat = detRes.sources
551 if detRes.background:
552 for bg
in detRes.background:
553 background.append(bg)
554 if self.config.doSkySources:
555 skySourceFootprints = self.skySources.run(mask=exposure.mask, seed=idGenerator.catalog_id)
556 if skySourceFootprints:
557 for foot
in skySourceFootprints:
558 s = sourceCat.addNew()
561 if self.config.doDeblend:
562 self.deblend.run(exposure=exposure, sources=sourceCat)
563 self.measurement.run(
566 exposureId=idGenerator.catalog_id,
568 if self.config.doApCorr:
569 apCorrMap = exposure.getInfo().getApCorrMap()
570 if apCorrMap
is None:
571 self.log.warning(
"Image does not have valid aperture correction map for %r; "
572 "skipping aperture correction", idGenerator)
574 self.applyApCorr.run(
578 self.catalogCalculation.run(sourceCat)
580 self.setPrimaryFlags.run(sourceCat)
582 if icSourceCat
is not None and \
583 len(self.config.icSourceFieldsToCopy) > 0:
591 if not sourceCat.isContiguous():
592 sourceCat = sourceCat.copy(deep=
True)
598 if self.config.doAstrometry:
599 astromRes = self.astrometry.run(
603 astromMatches = astromRes.matches
604 matchMeta = astromRes.matchMeta
605 if exposure.getWcs()
is None:
606 if self.config.requireAstrometry:
607 raise RuntimeError(f
"WCS fit failed for {idGenerator} and requireAstrometry "
610 self.log.warning(
"Unable to perform astrometric calibration for %r but "
611 "requireAstrometry is False: attempting to proceed...",
615 if self.config.doPhotoCal:
616 if np.all(np.isnan(sourceCat[
"coord_ra"]))
or np.all(np.isnan(sourceCat[
"coord_dec"])):
617 if self.config.requirePhotoCal:
618 raise RuntimeError(f
"Astrometry failed for {idGenerator}, so cannot do "
619 "photoCal, but requirePhotoCal is True.")
620 self.log.warning(
"Astrometry failed for %r, so cannot do photoCal. requirePhotoCal "
621 "is False, so skipping photometric calibration and setting photoCalib "
622 "to None. Attempting to proceed...", idGenerator)
623 exposure.setPhotoCalib(
None)
627 photoRes = self.photoCal.run(
628 exposure, sourceCat=sourceCat, expId=idGenerator.catalog_id
630 exposure.setPhotoCalib(photoRes.photoCalib)
633 self.log.info(
"Photometric zero-point: %f",
634 photoRes.photoCalib.instFluxToMagnitude(1.0))
635 self.
setMetadata(exposure=exposure, photoRes=photoRes)
636 except Exception
as e:
637 if self.config.requirePhotoCal:
639 self.log.warning(
"Unable to perform photometric calibration "
640 "(%s): attempting to proceed", e)
643 self.postCalibrationMeasurement.run(
646 exposureId=idGenerator.catalog_id,
649 if self.config.doComputeSummaryStats:
650 summary = self.computeSummaryStats.run(exposure=exposure,
652 background=background)
653 exposure.getInfo().setSummaryStats(summary)
655 frame = getDebugFrame(self._display,
"calibrate")
660 matches=astromMatches,
665 return pipeBase.Struct(
667 astromMatches=astromMatches,
669 outputExposure=exposure,
671 outputBackground=background,
711 """Match sources in an icSourceCat and a sourceCat and copy fields.
713 The fields copied are those specified by
714 ``config.icSourceFieldsToCopy``.
718 icSourceCat : `lsst.afw.table.SourceCatalog`
719 Catalog from which to copy fields.
720 sourceCat : `lsst.afw.table.SourceCatalog`
721 Catalog to which to copy fields.
726 Raised if any of the following occur:
727 - icSourceSchema and icSourceKeys are not specified.
728 - icSourceCat and sourceCat are not specified.
729 - icSourceFieldsToCopy is empty.
732 raise RuntimeError(
"To copy icSource fields you must specify "
733 "icSourceSchema and icSourceKeys when "
734 "constructing this task")
735 if icSourceCat
is None or sourceCat
is None:
736 raise RuntimeError(
"icSourceCat and sourceCat must both be "
738 if len(self.config.icSourceFieldsToCopy) == 0:
739 self.log.warning(
"copyIcSourceFields doing nothing because "
740 "icSourceFieldsToCopy is empty")
743 mc = afwTable.MatchControl()
744 mc.findOnlyClosest =
False
745 matches = afwTable.matchXy(icSourceCat, sourceCat,
746 self.config.matchRadiusPix, mc)
747 if self.config.doDeblend:
748 deblendKey = sourceCat.schema[
"deblend_nChild"].asKey()
750 matches = [m
for m
in matches
if m[1].get(deblendKey) == 0]
757 for m0, m1, d
in matches:
759 match = bestMatches.get(id0)
760 if match
is None or d <= match[2]:
761 bestMatches[id0] = (m0, m1, d)
762 matches = list(bestMatches.values())
767 numMatches = len(matches)
768 numUniqueSources = len(set(m[1].getId()
for m
in matches))
769 if numUniqueSources != numMatches:
770 self.log.warning(
"%d icSourceCat sources matched only %d sourceCat "
771 "sources", numMatches, numUniqueSources)
773 self.log.info(
"Copying flags from icSourceCat to sourceCat for "
774 "%d sources", numMatches)
778 for icSrc, src, d
in matches:
784 icSrcFootprint = icSrc.getFootprint()
786 icSrc.setFootprint(src.getFootprint())
789 icSrc.setFootprint(icSrcFootprint)