378 photoRefObjLoader=None, icSourceSchema=None,
379 initInputs=None, **kwargs):
382 if initInputs
is not None:
383 icSourceSchema = initInputs[
'icSourceSchema'].schema
385 if icSourceSchema
is not None:
388 minimumSchema = afwTable.SourceTable.makeMinimalSchema()
389 self.
schemaMapper.addMinimalSchema(minimumSchema,
False)
397 afwTable.Field[
"Flag"](
"calib_detected",
398 "Source was detected as an icSource"))
399 missingFieldNames = []
400 for fieldName
in self.config.icSourceFieldsToCopy:
402 schemaItem = icSourceSchema.find(fieldName)
404 missingFieldNames.append(fieldName)
409 if missingFieldNames:
410 raise RuntimeError(
"isSourceCat is missing fields {} "
411 "specified in icSourceFieldsToCopy"
412 .format(missingFieldNames))
419 self.
schema = afwTable.SourceTable.makeMinimalSchema()
420 afwTable.CoordKey.addErrorFields(self.
schema)
421 self.makeSubtask(
'detection', schema=self.
schema)
425 if self.config.doDeblend:
426 self.makeSubtask(
"deblend", schema=self.
schema)
427 if self.config.doSkySources:
428 self.makeSubtask(
"skySources")
430 self.makeSubtask(
'measurement', schema=self.
schema,
432 self.makeSubtask(
'postCalibrationMeasurement', schema=self.
schema,
434 self.makeSubtask(
"setPrimaryFlags", schema=self.
schema, isSingleFrame=
True)
435 if self.config.doApCorr:
436 self.makeSubtask(
'applyApCorr', schema=self.
schema)
437 self.makeSubtask(
'catalogCalculation', schema=self.
schema)
439 if self.config.doAstrometry:
440 self.makeSubtask(
"astrometry", refObjLoader=astromRefObjLoader,
442 if self.config.doPhotoCal:
443 self.makeSubtask(
"photoCal", refObjLoader=photoRefObjLoader,
445 if self.config.doComputeSummaryStats:
446 self.makeSubtask(
'computeSummaryStats')
448 if initInputs
is not None and (astromRefObjLoader
is not None or photoRefObjLoader
is not None):
449 raise RuntimeError(
"PipelineTask form of this task should not be initialized with "
450 "reference object loaders.")
455 self.
schema.checkUnits(parse_strict=self.config.checkUnitsParseStrict)
457 sourceCatSchema = afwTable.SourceCatalog(self.
schema)
462 inputs = butlerQC.get(inputRefs)
463 inputs[
'idGenerator'] = self.config.idGenerator.apply(butlerQC.quantum.dataId)
465 if self.config.doAstrometry:
466 refObjLoader = ReferenceObjectLoader(dataIds=[ref.datasetRef.dataId
467 for ref
in inputRefs.astromRefCat],
468 refCats=inputs.pop(
'astromRefCat'),
469 name=self.config.connections.astromRefCat,
470 config=self.config.astromRefObjLoader, log=self.log)
471 self.astrometry.setRefObjLoader(refObjLoader)
473 if self.config.doPhotoCal:
474 photoRefObjLoader = ReferenceObjectLoader(dataIds=[ref.datasetRef.dataId
475 for ref
in inputRefs.photoRefCat],
476 refCats=inputs.pop(
'photoRefCat'),
477 name=self.config.connections.photoRefCat,
478 config=self.config.photoRefObjLoader,
480 self.photoCal.match.setRefObjLoader(photoRefObjLoader)
482 outputs = self.
run(**inputs)
484 if self.config.doWriteMatches
and self.config.doAstrometry:
485 if outputs.astromMatches
is not None:
486 normalizedMatches = afwTable.packMatches(outputs.astromMatches)
487 normalizedMatches.table.setMetadata(outputs.matchMeta)
488 if self.config.doWriteMatchesDenormalized:
489 denormMatches = denormalizeMatches(outputs.astromMatches, outputs.matchMeta)
490 outputs.matchesDenormalized = denormMatches
491 outputs.matches = normalizedMatches
493 del outputRefs.matches
494 if self.config.doWriteMatchesDenormalized:
495 del outputRefs.matchesDenormalized
496 butlerQC.put(outputs, outputRefs)
499 def run(self, exposure, exposureIdInfo=None, background=None,
500 icSourceCat=None, idGenerator=None):
501 """Calibrate an exposure.
505 exposure : `lsst.afw.image.ExposureF`
506 Exposure to calibrate.
507 exposureIdInfo : `lsst.obs.baseExposureIdInfo`, optional
508 Exposure ID info. Deprecated in favor of ``idGenerator``, and
509 ignored if that is provided.
510 background : `lsst.afw.math.BackgroundList`, optional
511 Initial model of background already subtracted from exposure.
512 icSourceCat : `lsst.afw.image.SourceCatalog`, optional
513 SourceCatalog from CharacterizeImageTask from which we can copy
515 idGenerator : `lsst.meas.base.IdGenerator`, optional
516 Object that generates source IDs and provides RNG seeds.
520 result : `lsst.pipe.base.Struct`
521 Results as a struct with attributes:
524 Characterized exposure (`lsst.afw.image.ExposureF`).
526 Detected sources (`lsst.afw.table.SourceCatalog`).
528 Model of subtracted background (`lsst.afw.math.BackgroundList`).
530 List of source/ref matches from astrometry solver.
532 Metadata from astrometry matches.
534 Another reference to ``exposure`` for compatibility.
536 Another reference to ``sourceCat`` for compatibility.
539 if idGenerator
is None:
540 if exposureIdInfo
is not None:
541 idGenerator = IdGenerator._from_exposure_id_info(exposureIdInfo)
543 idGenerator = IdGenerator()
545 if background
is None:
546 background = BackgroundList()
547 table = SourceTable.make(self.
schema, idGenerator.make_table_id_factory())
550 detRes = self.detection.run(table=table, exposure=exposure,
552 sourceCat = detRes.sources
553 if detRes.background:
554 for bg
in detRes.background:
555 background.append(bg)
556 if self.config.doSkySources:
557 skySourceFootprints = self.skySources.run(mask=exposure.mask, seed=idGenerator.catalog_id)
558 if skySourceFootprints:
559 for foot
in skySourceFootprints:
560 s = sourceCat.addNew()
563 if self.config.doDeblend:
564 self.deblend.run(exposure=exposure, sources=sourceCat)
565 self.measurement.run(
568 exposureId=idGenerator.catalog_id,
570 if self.config.doApCorr:
571 apCorrMap = exposure.getInfo().getApCorrMap()
572 if apCorrMap
is None:
573 self.log.warning(
"Image does not have valid aperture correction map for %r; "
574 "skipping aperture correction", idGenerator)
576 self.applyApCorr.run(
580 self.catalogCalculation.run(sourceCat)
582 self.setPrimaryFlags.run(sourceCat)
584 if icSourceCat
is not None and \
585 len(self.config.icSourceFieldsToCopy) > 0:
593 if not sourceCat.isContiguous():
594 sourceCat = sourceCat.copy(deep=
True)
600 if self.config.doAstrometry:
601 astromRes = self.astrometry.run(
605 astromMatches = astromRes.matches
606 matchMeta = astromRes.matchMeta
607 if exposure.getWcs()
is None:
608 if self.config.requireAstrometry:
609 raise RuntimeError(f
"WCS fit failed for {idGenerator} and requireAstrometry "
612 self.log.warning(
"Unable to perform astrometric calibration for %r but "
613 "requireAstrometry is False: attempting to proceed...",
617 if self.config.doPhotoCal:
618 if np.all(np.isnan(sourceCat[
"coord_ra"]))
or np.all(np.isnan(sourceCat[
"coord_dec"])):
619 if self.config.requirePhotoCal:
620 raise RuntimeError(f
"Astrometry failed for {idGenerator}, so cannot do "
621 "photoCal, but requirePhotoCal is True.")
622 self.log.warning(
"Astrometry failed for %r, so cannot do photoCal. requirePhotoCal "
623 "is False, so skipping photometric calibration and setting photoCalib "
624 "to None. Attempting to proceed...", idGenerator)
625 exposure.setPhotoCalib(
None)
629 photoRes = self.photoCal.run(
630 exposure, sourceCat=sourceCat, expId=idGenerator.catalog_id
632 exposure.setPhotoCalib(photoRes.photoCalib)
635 self.log.info(
"Photometric zero-point: %f",
636 photoRes.photoCalib.instFluxToMagnitude(1.0))
637 self.
setMetadata(exposure=exposure, photoRes=photoRes)
638 except Exception
as e:
639 if self.config.requirePhotoCal:
641 self.log.warning(
"Unable to perform photometric calibration "
642 "(%s): attempting to proceed", e)
645 self.postCalibrationMeasurement.run(
648 exposureId=idGenerator.catalog_id,
651 if self.config.doComputeSummaryStats:
652 summary = self.computeSummaryStats.run(exposure=exposure,
654 background=background)
655 exposure.getInfo().setSummaryStats(summary)
657 frame = getDebugFrame(self._display,
"calibrate")
662 matches=astromMatches,
667 return pipeBase.Struct(
669 astromMatches=astromMatches,
671 outputExposure=exposure,
673 outputBackground=background,
713 """Match sources in an icSourceCat and a sourceCat and copy fields.
715 The fields copied are those specified by
716 ``config.icSourceFieldsToCopy``.
720 icSourceCat : `lsst.afw.table.SourceCatalog`
721 Catalog from which to copy fields.
722 sourceCat : `lsst.afw.table.SourceCatalog`
723 Catalog to which to copy fields.
728 Raised if any of the following occur:
729 - icSourceSchema and icSourceKeys are not specified.
730 - icSourceCat and sourceCat are not specified.
731 - icSourceFieldsToCopy is empty.
734 raise RuntimeError(
"To copy icSource fields you must specify "
735 "icSourceSchema and icSourceKeys when "
736 "constructing this task")
737 if icSourceCat
is None or sourceCat
is None:
738 raise RuntimeError(
"icSourceCat and sourceCat must both be "
740 if len(self.config.icSourceFieldsToCopy) == 0:
741 self.log.warning(
"copyIcSourceFields doing nothing because "
742 "icSourceFieldsToCopy is empty")
745 mc = afwTable.MatchControl()
746 mc.findOnlyClosest =
False
747 matches = afwTable.matchXy(icSourceCat, sourceCat,
748 self.config.matchRadiusPix, mc)
749 if self.config.doDeblend:
750 deblendKey = sourceCat.schema[
"deblend_nChild"].asKey()
752 matches = [m
for m
in matches
if m[1].get(deblendKey) == 0]
759 for m0, m1, d
in matches:
761 match = bestMatches.get(id0)
762 if match
is None or d <= match[2]:
763 bestMatches[id0] = (m0, m1, d)
764 matches = list(bestMatches.values())
769 numMatches = len(matches)
770 numUniqueSources = len(set(m[1].getId()
for m
in matches))
771 if numUniqueSources != numMatches:
772 self.log.warning(
"%d icSourceCat sources matched only %d sourceCat "
773 "sources", numMatches, numUniqueSources)
775 self.log.info(
"Copying flags from icSourceCat to sourceCat for "
776 "%d sources", numMatches)
780 for icSrc, src, d
in matches:
786 icSrcFootprint = icSrc.getFootprint()
788 icSrc.setFootprint(src.getFootprint())
791 icSrc.setFootprint(icSrcFootprint)