24 from lsstDebug
import getDebugFrame
25 import lsst.pex.config
as pexConfig
29 from lsst.meas.astrom import AstrometryTask, displayAstrometry, denormalizeMatches
38 CatalogCalculationTask)
40 from .fakes
import BaseFakeSourcesTask
41 from .photoCal
import PhotoCalTask
43 __all__ = [
"CalibrateConfig",
"CalibrateTask"]
46 class CalibrateConnections(pipeBase.PipelineTaskConnections, dimensions=(
"instrument",
"visit",
"detector"),
49 icSourceSchema = cT.InitInput(
50 doc=
"Schema produced by characterize image task, used to initialize this task",
52 storageClass=
"SourceCatalog",
55 outputSchema = cT.InitOutput(
56 doc=
"Schema after CalibrateTask has been initialized",
58 storageClass=
"SourceCatalog",
62 doc=
"Input image to calibrate",
64 storageClass=
"ExposureF",
65 dimensions=(
"instrument",
"visit",
"detector"),
68 background = cT.Input(
69 doc=
"Backgrounds determined by characterize task",
70 name=
"icExpBackground",
71 storageClass=
"Background",
72 dimensions=(
"instrument",
"visit",
"detector"),
75 icSourceCat = cT.Input(
76 doc=
"Source catalog created by characterize task",
78 storageClass=
"SourceCatalog",
79 dimensions=(
"instrument",
"visit",
"detector"),
82 astromRefCat = cT.PrerequisiteInput(
83 doc=
"Reference catalog to use for astrometry",
85 storageClass=
"SimpleCatalog",
86 dimensions=(
"skypix",),
91 photoRefCat = cT.PrerequisiteInput(
92 doc=
"Reference catalog to use for photometric calibration",
94 storageClass=
"SimpleCatalog",
95 dimensions=(
"skypix",),
100 outputExposure = cT.Output(
101 doc=
"Exposure after running calibration task",
103 storageClass=
"ExposureF",
104 dimensions=(
"instrument",
"visit",
"detector"),
107 outputCat = cT.Output(
108 doc=
"Source catalog produced in calibrate task",
110 storageClass=
"SourceCatalog",
111 dimensions=(
"instrument",
"visit",
"detector"),
114 outputBackground = cT.Output(
115 doc=
"Background models estimated in calibration task",
116 name=
"calexpBackground",
117 storageClass=
"Background",
118 dimensions=(
"instrument",
"visit",
"detector"),
122 doc=
"Source/refObj matches from the astrometry solver",
124 storageClass=
"Catalog",
125 dimensions=(
"instrument",
"visit",
"detector"),
128 matchesDenormalized = cT.Output(
129 doc=
"Denormalized matches from astrometry solver",
131 storageClass=
"Catalog",
132 dimensions=(
"instrument",
"visit",
"detector"),
137 if config.doWriteMatches
is False:
138 self.outputs.remove(
"matches")
139 if config.doWriteMatchesDenormalized
is False:
140 self.outputs.remove(
"matchesDenormalized")
143 class CalibrateConfig(pipeBase.PipelineTaskConfig, pipelineConnections=CalibrateConnections):
144 """Config for CalibrateTask"""
145 doWrite = pexConfig.Field(
148 doc=
"Save calibration results?",
150 doWriteHeavyFootprintsInSources = pexConfig.Field(
153 doc=
"Include HeavyFootprint data in source table? If false then heavy "
154 "footprints are saved as normal footprints, which saves some space"
156 doWriteMatches = pexConfig.Field(
159 doc=
"Write reference matches (ignored if doWrite false)?",
161 doWriteMatchesDenormalized = pexConfig.Field(
164 doc=(
"Write reference matches in denormalized format? "
165 "This format uses more disk space, but is more convenient to "
166 "read. Ignored if doWriteMatches=False or doWrite=False."),
168 doAstrometry = pexConfig.Field(
171 doc=
"Perform astrometric calibration?",
173 astromRefObjLoader = pexConfig.ConfigurableField(
174 target=LoadIndexedReferenceObjectsTask,
175 doc=
"reference object loader for astrometric calibration",
177 photoRefObjLoader = pexConfig.ConfigurableField(
178 target=LoadIndexedReferenceObjectsTask,
179 doc=
"reference object loader for photometric calibration",
181 astrometry = pexConfig.ConfigurableField(
182 target=AstrometryTask,
183 doc=
"Perform astrometric calibration to refine the WCS",
185 requireAstrometry = pexConfig.Field(
188 doc=(
"Raise an exception if astrometry fails? Ignored if doAstrometry "
191 doPhotoCal = pexConfig.Field(
194 doc=
"Perform phometric calibration?",
196 requirePhotoCal = pexConfig.Field(
199 doc=(
"Raise an exception if photoCal fails? Ignored if doPhotoCal "
202 photoCal = pexConfig.ConfigurableField(
204 doc=
"Perform photometric calibration",
206 icSourceFieldsToCopy = pexConfig.ListField(
208 default=(
"calib_psf_candidate",
"calib_psf_used",
"calib_psf_reserved"),
209 doc=(
"Fields to copy from the icSource catalog to the output catalog "
210 "for matching sources Any missing fields will trigger a "
211 "RuntimeError exception. Ignored if icSourceCat is not provided.")
213 matchRadiusPix = pexConfig.Field(
216 doc=(
"Match radius for matching icSourceCat objects to sourceCat "
219 checkUnitsParseStrict = pexConfig.Field(
220 doc=(
"Strictness of Astropy unit compatibility check, can be 'raise', "
221 "'warn' or 'silent'"),
225 detection = pexConfig.ConfigurableField(
226 target=SourceDetectionTask,
229 doDeblend = pexConfig.Field(
232 doc=
"Run deblender input exposure"
234 deblend = pexConfig.ConfigurableField(
235 target=SourceDeblendTask,
236 doc=
"Split blended sources into their components"
238 measurement = pexConfig.ConfigurableField(
239 target=SingleFrameMeasurementTask,
240 doc=
"Measure sources"
242 doApCorr = pexConfig.Field(
245 doc=
"Run subtask to apply aperture correction"
247 applyApCorr = pexConfig.ConfigurableField(
248 target=ApplyApCorrTask,
249 doc=
"Subtask to apply aperture corrections"
254 catalogCalculation = pexConfig.ConfigurableField(
255 target=CatalogCalculationTask,
256 doc=
"Subtask to run catalogCalculation plugins on catalog"
258 doInsertFakes = pexConfig.Field(
261 doc=
"Run fake sources injection task"
263 insertFakes = pexConfig.ConfigurableField(
264 target=BaseFakeSourcesTask,
265 doc=
"Injection of fake sources for testing purposes (must be "
268 doWriteExposure = pexConfig.Field(
271 doc=
"Write the calexp? If fakes have been added then we do not want to write out the calexp as a "
272 "normal calexp but as a fakes_calexp."
277 self.
detection.doTempLocalBackground =
False
278 self.
deblend.maxFootprintSize = 2000
283 if astromRefCatGen2
is not None and astromRefCatGen2 != self.connections.astromRefCat:
285 f
"Gen2 ({astromRefCatGen2}) and Gen3 ({self.connections.astromRefCat}) astrometry reference "
286 f
"catalogs are different. These options must be kept in sync until Gen2 is retired."
289 if photoRefCatGen2
is not None and photoRefCatGen2 != self.connections.photoRefCat:
291 f
"Gen2 ({photoRefCatGen2}) and Gen3 ({self.connections.photoRefCat}) photometry reference "
292 f
"catalogs are different. These options must be kept in sync until Gen2 is retired."
304 r"""!Calibrate an exposure: measure sources and perform astrometric and
305 photometric calibration
307 @anchor CalibrateTask_
309 @section pipe_tasks_calibrate_Contents Contents
311 - @ref pipe_tasks_calibrate_Purpose
312 - @ref pipe_tasks_calibrate_Initialize
313 - @ref pipe_tasks_calibrate_IO
314 - @ref pipe_tasks_calibrate_Config
315 - @ref pipe_tasks_calibrate_Metadata
316 - @ref pipe_tasks_calibrate_Debug
319 @section pipe_tasks_calibrate_Purpose Description
321 Given an exposure with a good PSF model and aperture correction map
322 (e.g. as provided by @ref CharacterizeImageTask), perform the following
324 - Run detection and measurement
325 - Run astrometry subtask to fit an improved WCS
326 - Run photoCal subtask to fit the exposure's photometric zero-point
328 @section pipe_tasks_calibrate_Initialize Task initialisation
330 @copydoc \_\_init\_\_
332 @section pipe_tasks_calibrate_IO Invoking the Task
334 If you want this task to unpersist inputs or persist outputs, then call
335 the `runDataRef` method (a wrapper around the `run` method).
337 If you already have the inputs unpersisted and do not want to persist the
338 output then it is more direct to call the `run` method:
340 @section pipe_tasks_calibrate_Config Configuration parameters
342 See @ref CalibrateConfig
344 @section pipe_tasks_calibrate_Metadata Quantities set in exposure Metadata
348 <dt>MAGZERO_RMS <dd>MAGZERO's RMS == sigma reported by photoCal task
349 <dt>MAGZERO_NOBJ <dd>Number of stars used == ngood reported by photoCal
351 <dt>COLORTERM1 <dd>?? (always 0.0)
352 <dt>COLORTERM2 <dd>?? (always 0.0)
353 <dt>COLORTERM3 <dd>?? (always 0.0)
356 @section pipe_tasks_calibrate_Debug Debug variables
358 The @link lsst.pipe.base.cmdLineTask.CmdLineTask command line task@endlink
359 interface supports a flag
360 `--debug` to import `debug.py` from your `$PYTHONPATH`; see @ref baseDebug
361 for more about `debug.py`.
363 CalibrateTask has a debug dictionary containing one key:
366 <dd>frame (an int; <= 0 to not display) in which to display the exposure,
367 sources and matches. See @ref lsst.meas.astrom.displayAstrometry for
368 the meaning of the various symbols.
371 For example, put something like:
375 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would
376 # call us recursively
377 if name == "lsst.pipe.tasks.calibrate":
384 lsstDebug.Info = DebugInfo
386 into your `debug.py` file and run `calibrateTask.py` with the `--debug`
389 Some subtasks may have their own debug variables; see individual Task
396 ConfigClass = CalibrateConfig
397 _DefaultName =
"calibrate"
398 RunnerClass = pipeBase.ButlerInitializedTaskRunner
400 def __init__(self, butler=None, astromRefObjLoader=None,
401 photoRefObjLoader=None, icSourceSchema=None,
402 initInputs=None, **kwargs):
403 """!Construct a CalibrateTask
405 @param[in] butler The butler is passed to the refObjLoader constructor
406 in case it is needed. Ignored if the refObjLoader argument
407 provides a loader directly.
408 @param[in] astromRefObjLoader An instance of LoadReferenceObjectsTasks
409 that supplies an external reference catalog for astrometric
410 calibration. May be None if the desired loader can be constructed
411 from the butler argument or all steps requiring a reference catalog
413 @param[in] photoRefObjLoader An instance of LoadReferenceObjectsTasks
414 that supplies an external reference catalog for photometric
415 calibration. May be None if the desired loader can be constructed
416 from the butler argument or all steps requiring a reference catalog
418 @param[in] icSourceSchema schema for icSource catalog, or None.
419 Schema values specified in config.icSourceFieldsToCopy will be
420 taken from this schema. If set to None, no values will be
421 propagated from the icSourceCatalog
422 @param[in,out] kwargs other keyword arguments for
423 lsst.pipe.base.CmdLineTask
427 if icSourceSchema
is None and butler
is not None:
429 icSourceSchema = butler.get(
"icSrc_schema", immediate=
True).schema
431 if icSourceSchema
is None and butler
is None and initInputs
is not None:
432 icSourceSchema = initInputs[
'icSourceSchema'].schema
434 if icSourceSchema
is not None:
436 self.
schemaMapper = afwTable.SchemaMapper(icSourceSchema)
437 minimumSchema = afwTable.SourceTable.makeMinimalSchema()
438 self.
schemaMapper.addMinimalSchema(minimumSchema,
False)
446 afwTable.Field[
"Flag"](
"calib_detected",
447 "Source was detected as an icSource"))
448 missingFieldNames = []
449 for fieldName
in self.config.icSourceFieldsToCopy:
451 schemaItem = icSourceSchema.find(fieldName)
453 missingFieldNames.append(fieldName)
458 if missingFieldNames:
459 raise RuntimeError(
"isSourceCat is missing fields {} "
460 "specified in icSourceFieldsToCopy"
461 .format(missingFieldNames))
468 self.
schema = afwTable.SourceTable.makeMinimalSchema()
469 self.makeSubtask(
'detection', schema=self.
schema)
476 if self.config.doInsertFakes:
477 self.makeSubtask(
"insertFakes")
479 if self.config.doDeblend:
480 self.makeSubtask(
"deblend", schema=self.
schema)
481 self.makeSubtask(
'measurement', schema=self.
schema,
483 if self.config.doApCorr:
484 self.makeSubtask(
'applyApCorr', schema=self.
schema)
485 self.makeSubtask(
'catalogCalculation', schema=self.
schema)
487 if self.config.doAstrometry:
488 if astromRefObjLoader
is None and butler
is not None:
489 self.makeSubtask(
'astromRefObjLoader', butler=butler)
490 astromRefObjLoader = self.astromRefObjLoader
491 self.
pixelMargin = astromRefObjLoader.config.pixelMargin
492 self.makeSubtask(
"astrometry", refObjLoader=astromRefObjLoader,
494 if self.config.doPhotoCal:
495 if photoRefObjLoader
is None and butler
is not None:
496 self.makeSubtask(
'photoRefObjLoader', butler=butler)
497 photoRefObjLoader = self.photoRefObjLoader
498 self.
pixelMargin = photoRefObjLoader.config.pixelMargin
499 self.makeSubtask(
"photoCal", refObjLoader=photoRefObjLoader,
502 if initInputs
is not None and (astromRefObjLoader
is not None or photoRefObjLoader
is not None):
503 raise RuntimeError(
"PipelineTask form of this task should not be initialized with "
504 "reference object loaders.")
509 self.
schema.checkUnits(parse_strict=self.config.checkUnitsParseStrict)
511 sourceCatSchema = afwTable.SourceCatalog(self.
schema)
516 def runDataRef(self, dataRef, exposure=None, background=None, icSourceCat=None,
518 """!Calibrate an exposure, optionally unpersisting inputs and
521 This is a wrapper around the `run` method that unpersists inputs
522 (if `doUnpersist` true) and persists outputs (if `config.doWrite` true)
524 @param[in] dataRef butler data reference corresponding to a science
526 @param[in,out] exposure characterized exposure (an
527 lsst.afw.image.ExposureF or similar), or None to unpersist existing
528 icExp and icBackground. See `run` method for details of what is
530 @param[in,out] background initial model of background already
531 subtracted from exposure (an lsst.afw.math.BackgroundList). May be
532 None if no background has been subtracted, though that is unusual
533 for calibration. A refined background model is output. Ignored if
535 @param[in] icSourceCat catalog from which to copy the fields specified
536 by icSourceKeys, or None;
537 @param[in] doUnpersist unpersist data:
538 - if True, exposure, background and icSourceCat are read from
539 dataRef and those three arguments must all be None;
540 - if False the exposure must be provided; background and
541 icSourceCat are optional. True is intended for running as a
542 command-line task, False for running as a subtask
543 @return same data as the calibrate method
545 self.log.info(
"Processing %s" % (dataRef.dataId))
548 if any(item
is not None for item
in (exposure, background,
550 raise RuntimeError(
"doUnpersist true; exposure, background "
551 "and icSourceCat must all be None")
552 exposure = dataRef.get(
"icExp", immediate=
True)
553 background = dataRef.get(
"icExpBackground", immediate=
True)
554 icSourceCat = dataRef.get(
"icSrc", immediate=
True)
555 elif exposure
is None:
556 raise RuntimeError(
"doUnpersist false; exposure must be provided")
558 exposureIdInfo = dataRef.get(
"expIdInfo")
562 exposureIdInfo=exposureIdInfo,
563 background=background,
564 icSourceCat=icSourceCat,
567 if self.config.doWrite:
570 exposure=calRes.exposure,
571 background=calRes.background,
572 sourceCat=calRes.sourceCat,
573 astromMatches=calRes.astromMatches,
574 matchMeta=calRes.matchMeta,
580 inputs = butlerQC.get(inputRefs)
581 expId, expBits = butlerQC.quantum.dataId.pack(
"visit_detector",
583 inputs[
'exposureIdInfo'] = ExposureIdInfo(expId, expBits)
585 if self.config.doAstrometry:
586 refObjLoader = ReferenceObjectLoader(dataIds=[ref.datasetRef.dataId
587 for ref
in inputRefs.astromRefCat],
588 refCats=inputs.pop(
'astromRefCat'),
589 config=self.config.astromRefObjLoader, log=self.log)
591 self.astrometry.setRefObjLoader(refObjLoader)
593 if self.config.doPhotoCal:
594 photoRefObjLoader = ReferenceObjectLoader(dataIds=[ref.datasetRef.dataId
595 for ref
in inputRefs.photoRefCat],
596 refCats=inputs.pop(
'photoRefCat'),
597 config=self.config.photoRefObjLoader,
599 self.
pixelMargin = photoRefObjLoader.config.pixelMargin
600 self.photoCal.match.setRefObjLoader(photoRefObjLoader)
602 outputs = self.
run(**inputs)
604 if self.config.doWriteMatches:
605 normalizedMatches = afwTable.packMatches(outputs.astromMatches)
606 normalizedMatches.table.setMetadata(outputs.matchMeta)
607 if self.config.doWriteMatchesDenormalized:
608 denormMatches = denormalizeMatches(outputs.astromMatches, outputs.matchMeta)
609 outputs.matchesDenormalized = denormMatches
610 outputs.matches = normalizedMatches
611 butlerQC.put(outputs, outputRefs)
613 def run(self, exposure, exposureIdInfo=None, background=None,
615 """!Calibrate an exposure (science image or coadd)
617 @param[in,out] exposure exposure to calibrate (an
618 lsst.afw.image.ExposureF or similar);
623 - MaskedImage has background subtracted
625 - PhotoCalib is replaced
626 @param[in] exposureIdInfo ID info for exposure (an
627 lsst.obs.base.ExposureIdInfo) If not provided, returned
628 SourceCatalog IDs will not be globally unique.
629 @param[in,out] background background model already subtracted from
630 exposure (an lsst.afw.math.BackgroundList). May be None if no
631 background has been subtracted, though that is unusual for
632 calibration. A refined background model is output.
633 @param[in] icSourceCat A SourceCatalog from CharacterizeImageTask
634 from which we can copy some fields.
636 @return pipe_base Struct containing these fields:
637 - exposure calibrate science exposure with refined WCS and PhotoCalib
638 - background model of background subtracted from exposure (an
639 lsst.afw.math.BackgroundList)
640 - sourceCat catalog of measured sources
641 - astromMatches list of source/refObj matches from the astrometry
645 if exposureIdInfo
is None:
646 exposureIdInfo = ExposureIdInfo()
648 if background
is None:
649 background = BackgroundList()
650 sourceIdFactory = IdFactory.makeSource(exposureIdInfo.expId,
651 exposureIdInfo.unusedBits)
652 table = SourceTable.make(self.
schema, sourceIdFactory)
655 detRes = self.detection.
run(table=table, exposure=exposure,
657 sourceCat = detRes.sources
658 if detRes.fpSets.background:
659 for bg
in detRes.fpSets.background:
660 background.append(bg)
661 if self.config.doDeblend:
662 self.deblend.
run(exposure=exposure, sources=sourceCat)
663 self.measurement.
run(
666 exposureId=exposureIdInfo.expId
668 if self.config.doApCorr:
669 self.applyApCorr.
run(
671 apCorrMap=exposure.getInfo().getApCorrMap()
673 self.catalogCalculation.
run(sourceCat)
675 if icSourceCat
is not None and \
676 len(self.config.icSourceFieldsToCopy) > 0:
684 if not sourceCat.isContiguous():
685 sourceCat = sourceCat.copy(deep=
True)
691 if self.config.doAstrometry:
693 astromRes = self.astrometry.
run(
697 astromMatches = astromRes.matches
698 matchMeta = astromRes.matchMeta
699 except Exception
as e:
700 if self.config.requireAstrometry:
702 self.log.warn(
"Unable to perform astrometric calibration "
703 "(%s): attempting to proceed" % e)
706 if self.config.doPhotoCal:
708 photoRes = self.photoCal.
run(exposure, sourceCat=sourceCat, expId=exposureIdInfo.expId)
709 exposure.setPhotoCalib(photoRes.photoCalib)
711 self.log.info(
"Photometric zero-point: %f" %
712 photoRes.photoCalib.instFluxToMagnitude(1.0))
713 self.
setMetadata(exposure=exposure, photoRes=photoRes)
714 except Exception
as e:
715 if self.config.requirePhotoCal:
717 self.log.warn(
"Unable to perform photometric calibration "
718 "(%s): attempting to proceed" % e)
721 if self.config.doInsertFakes:
722 self.insertFakes.
run(exposure, background=background)
724 table = SourceTable.make(self.
schema, sourceIdFactory)
727 detRes = self.detection.
run(table=table, exposure=exposure,
729 sourceCat = detRes.sources
730 if detRes.fpSets.background:
731 for bg
in detRes.fpSets.background:
732 background.append(bg)
733 if self.config.doDeblend:
734 self.deblend.
run(exposure=exposure, sources=sourceCat)
735 self.measurement.
run(
738 exposureId=exposureIdInfo.expId
740 if self.config.doApCorr:
741 self.applyApCorr.
run(
743 apCorrMap=exposure.getInfo().getApCorrMap()
745 self.catalogCalculation.
run(sourceCat)
747 if icSourceCat
is not None and len(self.config.icSourceFieldsToCopy) > 0:
751 frame = getDebugFrame(self._display,
"calibrate")
756 matches=astromMatches,
761 return pipeBase.Struct(
763 background=background,
765 astromMatches=astromMatches,
769 outputExposure=exposure,
771 outputBackground=background,
775 astromMatches, matchMeta):
776 """Write output data to the output repository
778 @param[in] dataRef butler data reference corresponding to a science
780 @param[in] exposure exposure to write
781 @param[in] background background model for exposure
782 @param[in] sourceCat catalog of measured sources
783 @param[in] astromMatches list of source/refObj matches from the
786 dataRef.put(sourceCat,
"src")
787 if self.config.doWriteMatches
and astromMatches
is not None:
788 normalizedMatches = afwTable.packMatches(astromMatches)
789 normalizedMatches.table.setMetadata(matchMeta)
790 dataRef.put(normalizedMatches,
"srcMatch")
791 if self.config.doWriteMatchesDenormalized:
792 denormMatches = denormalizeMatches(astromMatches, matchMeta)
793 dataRef.put(denormMatches,
"srcMatchFull")
794 if self.config.doWriteExposure:
795 dataRef.put(exposure,
"calexp")
796 dataRef.put(background,
"calexpBackground")
799 """Return a dict of empty catalogs for each catalog dataset produced
802 sourceCat = afwTable.SourceCatalog(self.
schema)
804 return {
"src": sourceCat}
807 """!Set task and exposure metadata
809 Logs a warning and continues if needed data is missing.
811 @param[in,out] exposure exposure whose metadata is to be set
812 @param[in] photoRes results of running photoCal; if None then it was
818 metadata = exposure.getMetadata()
822 exposureTime = exposure.getInfo().getVisitInfo().getExposureTime()
823 magZero = photoRes.zp - 2.5*math.log10(exposureTime)
825 self.log.warn(
"Could not set normalized MAGZERO in header: no "
830 metadata.set(
'MAGZERO', magZero)
831 metadata.set(
'MAGZERO_RMS', photoRes.sigma)
832 metadata.set(
'MAGZERO_NOBJ', photoRes.ngood)
833 metadata.set(
'COLORTERM1', 0.0)
834 metadata.set(
'COLORTERM2', 0.0)
835 metadata.set(
'COLORTERM3', 0.0)
836 except Exception
as e:
837 self.log.warn(
"Could not set exposure metadata: %s" % (e,))
840 """!Match sources in icSourceCat and sourceCat and copy the specified fields
842 @param[in] icSourceCat catalog from which to copy fields
843 @param[in,out] sourceCat catalog to which to copy fields
845 The fields copied are those specified by `config.icSourceFieldsToCopy`
846 that actually exist in the schema. This was set up by the constructor
847 using self.schemaMapper.
850 raise RuntimeError(
"To copy icSource fields you must specify "
851 "icSourceSchema nd icSourceKeys when "
852 "constructing this task")
853 if icSourceCat
is None or sourceCat
is None:
854 raise RuntimeError(
"icSourceCat and sourceCat must both be "
856 if len(self.config.icSourceFieldsToCopy) == 0:
857 self.log.warn(
"copyIcSourceFields doing nothing because "
858 "icSourceFieldsToCopy is empty")
861 mc = afwTable.MatchControl()
862 mc.findOnlyClosest =
False
863 matches = afwTable.matchXy(icSourceCat, sourceCat,
864 self.config.matchRadiusPix, mc)
865 if self.config.doDeblend:
866 deblendKey = sourceCat.schema[
"deblend_nChild"].asKey()
868 matches = [m
for m
in matches
if m[1].get(deblendKey) == 0]
875 for m0, m1, d
in matches:
877 match = bestMatches.get(id0)
878 if match
is None or d <= match[2]:
879 bestMatches[id0] = (m0, m1, d)
880 matches = list(bestMatches.values())
885 numMatches = len(matches)
886 numUniqueSources = len(set(m[1].getId()
for m
in matches))
887 if numUniqueSources != numMatches:
888 self.log.warn(
"{} icSourceCat sources matched only {} sourceCat "
889 "sources".format(numMatches, numUniqueSources))
891 self.log.info(
"Copying flags from icSourceCat to sourceCat for "
892 "%s sources" % (numMatches,))
896 for icSrc, src, d
in matches:
902 icSrcFootprint = icSrc.getFootprint()
904 icSrc.setFootprint(src.getFootprint())
907 icSrc.setFootprint(icSrcFootprint)