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"]
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"),
138 if config.doAstrometry
is False:
139 self.prerequisiteInputs.remove(
"astromRefCat")
140 if config.doPhotoCal
is False:
141 self.prerequisiteInputs.remove(
"photoRefCat")
143 if config.doWriteMatches
is False or config.doAstrometry
is False:
144 self.outputs.remove(
"matches")
145 if config.doWriteMatchesDenormalized
is False or config.doAstrometry
is False:
146 self.outputs.remove(
"matchesDenormalized")
149 class CalibrateConfig(pipeBase.PipelineTaskConfig, pipelineConnections=CalibrateConnections):
150 """Config for CalibrateTask"""
151 doWrite = pexConfig.Field(
154 doc=
"Save calibration results?",
156 doWriteHeavyFootprintsInSources = pexConfig.Field(
159 doc=
"Include HeavyFootprint data in source table? If false then heavy "
160 "footprints are saved as normal footprints, which saves some space"
162 doWriteMatches = pexConfig.Field(
165 doc=
"Write reference matches (ignored if doWrite or doAstrometry false)?",
167 doWriteMatchesDenormalized = pexConfig.Field(
170 doc=(
"Write reference matches in denormalized format? "
171 "This format uses more disk space, but is more convenient to "
172 "read. Ignored if doWriteMatches=False or doWrite=False."),
174 doAstrometry = pexConfig.Field(
177 doc=
"Perform astrometric calibration?",
179 astromRefObjLoader = pexConfig.ConfigurableField(
180 target=LoadIndexedReferenceObjectsTask,
181 doc=
"reference object loader for astrometric calibration",
183 photoRefObjLoader = pexConfig.ConfigurableField(
184 target=LoadIndexedReferenceObjectsTask,
185 doc=
"reference object loader for photometric calibration",
187 astrometry = pexConfig.ConfigurableField(
188 target=AstrometryTask,
189 doc=
"Perform astrometric calibration to refine the WCS",
191 requireAstrometry = pexConfig.Field(
194 doc=(
"Raise an exception if astrometry fails? Ignored if doAstrometry "
197 doPhotoCal = pexConfig.Field(
200 doc=
"Perform phometric calibration?",
202 requirePhotoCal = pexConfig.Field(
205 doc=(
"Raise an exception if photoCal fails? Ignored if doPhotoCal "
208 photoCal = pexConfig.ConfigurableField(
210 doc=
"Perform photometric calibration",
212 icSourceFieldsToCopy = pexConfig.ListField(
214 default=(
"calib_psf_candidate",
"calib_psf_used",
"calib_psf_reserved"),
215 doc=(
"Fields to copy from the icSource catalog to the output catalog "
216 "for matching sources Any missing fields will trigger a "
217 "RuntimeError exception. Ignored if icSourceCat is not provided.")
219 matchRadiusPix = pexConfig.Field(
222 doc=(
"Match radius for matching icSourceCat objects to sourceCat "
225 checkUnitsParseStrict = pexConfig.Field(
226 doc=(
"Strictness of Astropy unit compatibility check, can be 'raise', "
227 "'warn' or 'silent'"),
231 detection = pexConfig.ConfigurableField(
232 target=SourceDetectionTask,
235 doDeblend = pexConfig.Field(
238 doc=
"Run deblender input exposure"
240 deblend = pexConfig.ConfigurableField(
241 target=SourceDeblendTask,
242 doc=
"Split blended sources into their components"
244 doSkySources = pexConfig.Field(
247 doc=
"Generate sky sources?",
249 skySources = pexConfig.ConfigurableField(
250 target=SkyObjectsTask,
251 doc=
"Generate sky sources",
253 measurement = pexConfig.ConfigurableField(
254 target=SingleFrameMeasurementTask,
255 doc=
"Measure sources"
257 doApCorr = pexConfig.Field(
260 doc=
"Run subtask to apply aperture correction"
262 applyApCorr = pexConfig.ConfigurableField(
263 target=ApplyApCorrTask,
264 doc=
"Subtask to apply aperture corrections"
269 catalogCalculation = pexConfig.ConfigurableField(
270 target=CatalogCalculationTask,
271 doc=
"Subtask to run catalogCalculation plugins on catalog"
273 doInsertFakes = pexConfig.Field(
276 doc=
"Run fake sources injection task"
278 insertFakes = pexConfig.ConfigurableField(
279 target=BaseFakeSourcesTask,
280 doc=
"Injection of fake sources for testing purposes (must be "
283 doWriteExposure = pexConfig.Field(
286 doc=
"Write the calexp? If fakes have been added then we do not want to write out the calexp as a "
287 "normal calexp but as a fakes_calexp."
292 self.
detection.doTempLocalBackground =
False
293 self.
deblend.maxFootprintSize = 2000
294 self.
measurement.plugins.names |= [
"base_LocalPhotoCalib",
"base_LocalWcs"]
299 if astromRefCatGen2
is not None and astromRefCatGen2 != self.connections.astromRefCat:
301 f
"Gen2 ({astromRefCatGen2}) and Gen3 ({self.connections.astromRefCat}) astrometry reference "
302 f
"catalogs are different. These options must be kept in sync until Gen2 is retired."
305 if photoRefCatGen2
is not None and photoRefCatGen2 != self.connections.photoRefCat:
307 f
"Gen2 ({photoRefCatGen2}) and Gen3 ({self.connections.photoRefCat}) photometry reference "
308 f
"catalogs are different. These options must be kept in sync until Gen2 is retired."
320 r"""!Calibrate an exposure: measure sources and perform astrometric and
321 photometric calibration
323 @anchor CalibrateTask_
325 @section pipe_tasks_calibrate_Contents Contents
327 - @ref pipe_tasks_calibrate_Purpose
328 - @ref pipe_tasks_calibrate_Initialize
329 - @ref pipe_tasks_calibrate_IO
330 - @ref pipe_tasks_calibrate_Config
331 - @ref pipe_tasks_calibrate_Metadata
332 - @ref pipe_tasks_calibrate_Debug
335 @section pipe_tasks_calibrate_Purpose Description
337 Given an exposure with a good PSF model and aperture correction map
338 (e.g. as provided by @ref CharacterizeImageTask), perform the following
340 - Run detection and measurement
341 - Run astrometry subtask to fit an improved WCS
342 - Run photoCal subtask to fit the exposure's photometric zero-point
344 @section pipe_tasks_calibrate_Initialize Task initialisation
346 @copydoc \_\_init\_\_
348 @section pipe_tasks_calibrate_IO Invoking the Task
350 If you want this task to unpersist inputs or persist outputs, then call
351 the `runDataRef` method (a wrapper around the `run` method).
353 If you already have the inputs unpersisted and do not want to persist the
354 output then it is more direct to call the `run` method:
356 @section pipe_tasks_calibrate_Config Configuration parameters
358 See @ref CalibrateConfig
360 @section pipe_tasks_calibrate_Metadata Quantities set in exposure Metadata
364 <dt>MAGZERO_RMS <dd>MAGZERO's RMS == sigma reported by photoCal task
365 <dt>MAGZERO_NOBJ <dd>Number of stars used == ngood reported by photoCal
367 <dt>COLORTERM1 <dd>?? (always 0.0)
368 <dt>COLORTERM2 <dd>?? (always 0.0)
369 <dt>COLORTERM3 <dd>?? (always 0.0)
372 @section pipe_tasks_calibrate_Debug Debug variables
374 The @link lsst.pipe.base.cmdLineTask.CmdLineTask command line task@endlink
375 interface supports a flag
376 `--debug` to import `debug.py` from your `$PYTHONPATH`; see @ref baseDebug
377 for more about `debug.py`.
379 CalibrateTask has a debug dictionary containing one key:
382 <dd>frame (an int; <= 0 to not display) in which to display the exposure,
383 sources and matches. See @ref lsst.meas.astrom.displayAstrometry for
384 the meaning of the various symbols.
387 For example, put something like:
391 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would
392 # call us recursively
393 if name == "lsst.pipe.tasks.calibrate":
400 lsstDebug.Info = DebugInfo
402 into your `debug.py` file and run `calibrateTask.py` with the `--debug`
405 Some subtasks may have their own debug variables; see individual Task
412 ConfigClass = CalibrateConfig
413 _DefaultName =
"calibrate"
414 RunnerClass = pipeBase.ButlerInitializedTaskRunner
416 def __init__(self, butler=None, astromRefObjLoader=None,
417 photoRefObjLoader=None, icSourceSchema=None,
418 initInputs=None, **kwargs):
419 """!Construct a CalibrateTask
421 @param[in] butler The butler is passed to the refObjLoader constructor
422 in case it is needed. Ignored if the refObjLoader argument
423 provides a loader directly.
424 @param[in] astromRefObjLoader An instance of LoadReferenceObjectsTasks
425 that supplies an external reference catalog for astrometric
426 calibration. May be None if the desired loader can be constructed
427 from the butler argument or all steps requiring a reference catalog
429 @param[in] photoRefObjLoader An instance of LoadReferenceObjectsTasks
430 that supplies an external reference catalog for photometric
431 calibration. May be None if the desired loader can be constructed
432 from the butler argument or all steps requiring a reference catalog
434 @param[in] icSourceSchema schema for icSource catalog, or None.
435 Schema values specified in config.icSourceFieldsToCopy will be
436 taken from this schema. If set to None, no values will be
437 propagated from the icSourceCatalog
438 @param[in,out] kwargs other keyword arguments for
439 lsst.pipe.base.CmdLineTask
443 if icSourceSchema
is None and butler
is not None:
445 icSourceSchema = butler.get(
"icSrc_schema", immediate=
True).schema
447 if icSourceSchema
is None and butler
is None and initInputs
is not None:
448 icSourceSchema = initInputs[
'icSourceSchema'].schema
450 if icSourceSchema
is not None:
453 minimumSchema = afwTable.SourceTable.makeMinimalSchema()
454 self.
schemaMapper.addMinimalSchema(minimumSchema,
False)
463 "Source was detected as an icSource"))
464 missingFieldNames = []
465 for fieldName
in self.config.icSourceFieldsToCopy:
467 schemaItem = icSourceSchema.find(fieldName)
469 missingFieldNames.append(fieldName)
474 if missingFieldNames:
475 raise RuntimeError(
"isSourceCat is missing fields {} "
476 "specified in icSourceFieldsToCopy"
477 .
format(missingFieldNames))
484 self.
schema = afwTable.SourceTable.makeMinimalSchema()
485 self.makeSubtask(
'detection', schema=self.
schema)
492 if self.config.doInsertFakes:
493 self.makeSubtask(
"insertFakes")
495 if self.config.doDeblend:
496 self.makeSubtask(
"deblend", schema=self.
schema)
497 if self.config.doSkySources:
498 self.makeSubtask(
"skySources")
499 self.
skySourceKey = self.
schema.addField(
"sky_source", type=
"Flag", doc=
"Sky objects.")
500 self.makeSubtask(
'measurement', schema=self.
schema,
502 if self.config.doApCorr:
503 self.makeSubtask(
'applyApCorr', schema=self.
schema)
504 self.makeSubtask(
'catalogCalculation', schema=self.
schema)
506 if self.config.doAstrometry:
507 if astromRefObjLoader
is None and butler
is not None:
508 self.makeSubtask(
'astromRefObjLoader', butler=butler)
509 astromRefObjLoader = self.astromRefObjLoader
510 self.
pixelMargin = astromRefObjLoader.config.pixelMargin
511 self.makeSubtask(
"astrometry", refObjLoader=astromRefObjLoader,
513 if self.config.doPhotoCal:
514 if photoRefObjLoader
is None and butler
is not None:
515 self.makeSubtask(
'photoRefObjLoader', butler=butler)
516 photoRefObjLoader = self.photoRefObjLoader
517 self.
pixelMargin = photoRefObjLoader.config.pixelMargin
518 self.makeSubtask(
"photoCal", refObjLoader=photoRefObjLoader,
521 if initInputs
is not None and (astromRefObjLoader
is not None or photoRefObjLoader
is not None):
522 raise RuntimeError(
"PipelineTask form of this task should not be initialized with "
523 "reference object loaders.")
528 self.
schema.checkUnits(parse_strict=self.config.checkUnitsParseStrict)
535 def runDataRef(self, dataRef, exposure=None, background=None, icSourceCat=None,
537 """!Calibrate an exposure, optionally unpersisting inputs and
540 This is a wrapper around the `run` method that unpersists inputs
541 (if `doUnpersist` true) and persists outputs (if `config.doWrite` true)
543 @param[in] dataRef butler data reference corresponding to a science
545 @param[in,out] exposure characterized exposure (an
546 lsst.afw.image.ExposureF or similar), or None to unpersist existing
547 icExp and icBackground. See `run` method for details of what is
549 @param[in,out] background initial model of background already
550 subtracted from exposure (an lsst.afw.math.BackgroundList). May be
551 None if no background has been subtracted, though that is unusual
552 for calibration. A refined background model is output. Ignored if
554 @param[in] icSourceCat catalog from which to copy the fields specified
555 by icSourceKeys, or None;
556 @param[in] doUnpersist unpersist data:
557 - if True, exposure, background and icSourceCat are read from
558 dataRef and those three arguments must all be None;
559 - if False the exposure must be provided; background and
560 icSourceCat are optional. True is intended for running as a
561 command-line task, False for running as a subtask
562 @return same data as the calibrate method
564 self.log.
info(
"Processing %s" % (dataRef.dataId))
567 if any(item
is not None for item
in (exposure, background,
569 raise RuntimeError(
"doUnpersist true; exposure, background "
570 "and icSourceCat must all be None")
571 exposure = dataRef.get(
"icExp", immediate=
True)
572 background = dataRef.get(
"icExpBackground", immediate=
True)
573 icSourceCat = dataRef.get(
"icSrc", immediate=
True)
574 elif exposure
is None:
575 raise RuntimeError(
"doUnpersist false; exposure must be provided")
577 exposureIdInfo = dataRef.get(
"expIdInfo")
581 exposureIdInfo=exposureIdInfo,
582 background=background,
583 icSourceCat=icSourceCat,
586 if self.config.doWrite:
589 exposure=calRes.exposure,
590 background=calRes.background,
591 sourceCat=calRes.sourceCat,
592 astromMatches=calRes.astromMatches,
593 matchMeta=calRes.matchMeta,
599 inputs = butlerQC.get(inputRefs)
600 expId, expBits = butlerQC.quantum.dataId.pack(
"visit_detector",
604 if self.config.doAstrometry:
606 for ref
in inputRefs.astromRefCat],
607 refCats=inputs.pop(
'astromRefCat'),
608 config=self.config.astromRefObjLoader, log=self.log)
610 self.astrometry.setRefObjLoader(refObjLoader)
612 if self.config.doPhotoCal:
614 for ref
in inputRefs.photoRefCat],
615 refCats=inputs.pop(
'photoRefCat'),
616 config=self.config.photoRefObjLoader,
618 self.
pixelMargin = photoRefObjLoader.config.pixelMargin
619 self.photoCal.match.setRefObjLoader(photoRefObjLoader)
621 outputs = self.
run(**inputs)
623 if self.config.doWriteMatches
and self.config.doAstrometry:
625 normalizedMatches.table.setMetadata(outputs.matchMeta)
626 if self.config.doWriteMatchesDenormalized:
628 outputs.matchesDenormalized = denormMatches
629 outputs.matches = normalizedMatches
630 butlerQC.put(outputs, outputRefs)
632 def run(self, exposure, exposureIdInfo=None, background=None,
634 """!Calibrate an exposure (science image or coadd)
636 @param[in,out] exposure exposure to calibrate (an
637 lsst.afw.image.ExposureF or similar);
642 - MaskedImage has background subtracted
644 - PhotoCalib is replaced
645 @param[in] exposureIdInfo ID info for exposure (an
646 lsst.obs.base.ExposureIdInfo) If not provided, returned
647 SourceCatalog IDs will not be globally unique.
648 @param[in,out] background background model already subtracted from
649 exposure (an lsst.afw.math.BackgroundList). May be None if no
650 background has been subtracted, though that is unusual for
651 calibration. A refined background model is output.
652 @param[in] icSourceCat A SourceCatalog from CharacterizeImageTask
653 from which we can copy some fields.
655 @return pipe_base Struct containing these fields:
656 - exposure calibrate science exposure with refined WCS and PhotoCalib
657 - background model of background subtracted from exposure (an
658 lsst.afw.math.BackgroundList)
659 - sourceCat catalog of measured sources
660 - astromMatches list of source/refObj matches from the astrometry
664 if exposureIdInfo
is None:
667 if background
is None:
669 sourceIdFactory = IdFactory.makeSource(exposureIdInfo.expId,
670 exposureIdInfo.unusedBits)
671 table = SourceTable.make(self.
schema, sourceIdFactory)
674 detRes = self.detection.
run(table=table, exposure=exposure,
676 sourceCat = detRes.sources
677 if detRes.fpSets.background:
678 for bg
in detRes.fpSets.background:
679 background.append(bg)
680 if self.config.doSkySources:
681 skySourceFootprints = self.skySources.
run(mask=exposure.mask, seed=exposureIdInfo.expId)
682 if skySourceFootprints:
683 for foot
in skySourceFootprints:
684 s = sourceCat.addNew()
687 if self.config.doDeblend:
688 self.deblend.
run(exposure=exposure, sources=sourceCat)
689 self.measurement.
run(
692 exposureId=exposureIdInfo.expId
694 if self.config.doApCorr:
695 self.applyApCorr.
run(
697 apCorrMap=exposure.getInfo().getApCorrMap()
699 self.catalogCalculation.
run(sourceCat)
701 if icSourceCat
is not None and \
702 len(self.config.icSourceFieldsToCopy) > 0:
710 if not sourceCat.isContiguous():
711 sourceCat = sourceCat.copy(deep=
True)
717 if self.config.doAstrometry:
719 astromRes = self.astrometry.
run(
723 astromMatches = astromRes.matches
724 matchMeta = astromRes.matchMeta
725 except Exception
as e:
726 if self.config.requireAstrometry:
728 self.log.
warn(
"Unable to perform astrometric calibration "
729 "(%s): attempting to proceed" % e)
732 if self.config.doPhotoCal:
734 photoRes = self.photoCal.
run(exposure, sourceCat=sourceCat, expId=exposureIdInfo.expId)
735 exposure.setPhotoCalib(photoRes.photoCalib)
737 self.log.
info(
"Photometric zero-point: %f" %
738 photoRes.photoCalib.instFluxToMagnitude(1.0))
739 self.
setMetadata(exposure=exposure, photoRes=photoRes)
740 except Exception
as e:
741 if self.config.requirePhotoCal:
743 self.log.
warn(
"Unable to perform photometric calibration "
744 "(%s): attempting to proceed" % e)
747 if self.config.doInsertFakes:
748 self.insertFakes.
run(exposure, background=background)
750 table = SourceTable.make(self.
schema, sourceIdFactory)
753 detRes = self.detection.
run(table=table, exposure=exposure,
755 sourceCat = detRes.sources
756 if detRes.fpSets.background:
757 for bg
in detRes.fpSets.background:
758 background.append(bg)
759 if self.config.doDeblend:
760 self.deblend.
run(exposure=exposure, sources=sourceCat)
761 self.measurement.
run(
764 exposureId=exposureIdInfo.expId
766 if self.config.doApCorr:
767 self.applyApCorr.
run(
769 apCorrMap=exposure.getInfo().getApCorrMap()
771 self.catalogCalculation.
run(sourceCat)
773 if icSourceCat
is not None and len(self.config.icSourceFieldsToCopy) > 0:
782 matches=astromMatches,
787 return pipeBase.Struct(
789 background=background,
791 astromMatches=astromMatches,
795 outputExposure=exposure,
797 outputBackground=background,
801 astromMatches, matchMeta):
802 """Write output data to the output repository
804 @param[in] dataRef butler data reference corresponding to a science
806 @param[in] exposure exposure to write
807 @param[in] background background model for exposure
808 @param[in] sourceCat catalog of measured sources
809 @param[in] astromMatches list of source/refObj matches from the
812 dataRef.put(sourceCat,
"src")
813 if self.config.doWriteMatches
and astromMatches
is not None:
815 normalizedMatches.table.setMetadata(matchMeta)
816 dataRef.put(normalizedMatches,
"srcMatch")
817 if self.config.doWriteMatchesDenormalized:
819 dataRef.put(denormMatches,
"srcMatchFull")
820 if self.config.doWriteExposure:
821 dataRef.put(exposure,
"calexp")
822 dataRef.put(background,
"calexpBackground")
825 """Return a dict of empty catalogs for each catalog dataset produced
830 return {
"src": sourceCat}
833 """!Set task and exposure metadata
835 Logs a warning and continues if needed data is missing.
837 @param[in,out] exposure exposure whose metadata is to be set
838 @param[in] photoRes results of running photoCal; if None then it was
844 metadata = exposure.getMetadata()
848 exposureTime = exposure.getInfo().getVisitInfo().getExposureTime()
849 magZero = photoRes.zp - 2.5*math.log10(exposureTime)
851 self.log.
warn(
"Could not set normalized MAGZERO in header: no "
856 metadata.set(
'MAGZERO', magZero)
857 metadata.set(
'MAGZERO_RMS', photoRes.sigma)
858 metadata.set(
'MAGZERO_NOBJ', photoRes.ngood)
859 metadata.set(
'COLORTERM1', 0.0)
860 metadata.set(
'COLORTERM2', 0.0)
861 metadata.set(
'COLORTERM3', 0.0)
862 except Exception
as e:
863 self.log.
warn(
"Could not set exposure metadata: %s" % (e,))
866 """!Match sources in icSourceCat and sourceCat and copy the specified fields
868 @param[in] icSourceCat catalog from which to copy fields
869 @param[in,out] sourceCat catalog to which to copy fields
871 The fields copied are those specified by `config.icSourceFieldsToCopy`
872 that actually exist in the schema. This was set up by the constructor
873 using self.schemaMapper.
876 raise RuntimeError(
"To copy icSource fields you must specify "
877 "icSourceSchema nd icSourceKeys when "
878 "constructing this task")
879 if icSourceCat
is None or sourceCat
is None:
880 raise RuntimeError(
"icSourceCat and sourceCat must both be "
882 if len(self.config.icSourceFieldsToCopy) == 0:
883 self.log.
warn(
"copyIcSourceFields doing nothing because "
884 "icSourceFieldsToCopy is empty")
888 mc.findOnlyClosest =
False
890 self.config.matchRadiusPix, mc)
891 if self.config.doDeblend:
892 deblendKey = sourceCat.schema[
"deblend_nChild"].asKey()
894 matches = [m
for m
in matches
if m[1].get(deblendKey) == 0]
901 for m0, m1, d
in matches:
903 match = bestMatches.get(id0)
904 if match
is None or d <= match[2]:
905 bestMatches[id0] = (m0, m1, d)
906 matches =
list(bestMatches.values())
911 numMatches = len(matches)
912 numUniqueSources = len(
set(m[1].getId()
for m
in matches))
913 if numUniqueSources != numMatches:
914 self.log.
warn(
"{} icSourceCat sources matched only {} sourceCat "
915 "sources".
format(numMatches, numUniqueSources))
917 self.log.
info(
"Copying flags from icSourceCat to sourceCat for "
918 "%s sources" % (numMatches,))
922 for icSrc, src, d
in matches:
928 icSrcFootprint = icSrc.getFootprint()
930 icSrc.setFootprint(src.getFootprint())
933 icSrc.setFootprint(icSrcFootprint)