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"),
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 measurement = pexConfig.ConfigurableField(
245 target=SingleFrameMeasurementTask,
246 doc=
"Measure sources"
248 doApCorr = pexConfig.Field(
251 doc=
"Run subtask to apply aperture correction"
253 applyApCorr = pexConfig.ConfigurableField(
254 target=ApplyApCorrTask,
255 doc=
"Subtask to apply aperture corrections"
260 catalogCalculation = pexConfig.ConfigurableField(
261 target=CatalogCalculationTask,
262 doc=
"Subtask to run catalogCalculation plugins on catalog"
264 doInsertFakes = pexConfig.Field(
267 doc=
"Run fake sources injection task"
269 insertFakes = pexConfig.ConfigurableField(
270 target=BaseFakeSourcesTask,
271 doc=
"Injection of fake sources for testing purposes (must be "
274 doWriteExposure = pexConfig.Field(
277 doc=
"Write the calexp? If fakes have been added then we do not want to write out the calexp as a "
278 "normal calexp but as a fakes_calexp."
283 self.
detection.doTempLocalBackground =
False
284 self.
deblend.maxFootprintSize = 2000
285 self.
measurement.plugins.names |= [
"base_LocalPhotoCalib",
"base_LocalWcs"]
290 if astromRefCatGen2
is not None and astromRefCatGen2 != self.connections.astromRefCat:
292 f
"Gen2 ({astromRefCatGen2}) and Gen3 ({self.connections.astromRefCat}) astrometry reference "
293 f
"catalogs are different. These options must be kept in sync until Gen2 is retired."
296 if photoRefCatGen2
is not None and photoRefCatGen2 != self.connections.photoRefCat:
298 f
"Gen2 ({photoRefCatGen2}) and Gen3 ({self.connections.photoRefCat}) photometry reference "
299 f
"catalogs are different. These options must be kept in sync until Gen2 is retired."
311 r"""!Calibrate an exposure: measure sources and perform astrometric and
312 photometric calibration
314 @anchor CalibrateTask_
316 @section pipe_tasks_calibrate_Contents Contents
318 - @ref pipe_tasks_calibrate_Purpose
319 - @ref pipe_tasks_calibrate_Initialize
320 - @ref pipe_tasks_calibrate_IO
321 - @ref pipe_tasks_calibrate_Config
322 - @ref pipe_tasks_calibrate_Metadata
323 - @ref pipe_tasks_calibrate_Debug
326 @section pipe_tasks_calibrate_Purpose Description
328 Given an exposure with a good PSF model and aperture correction map
329 (e.g. as provided by @ref CharacterizeImageTask), perform the following
331 - Run detection and measurement
332 - Run astrometry subtask to fit an improved WCS
333 - Run photoCal subtask to fit the exposure's photometric zero-point
335 @section pipe_tasks_calibrate_Initialize Task initialisation
337 @copydoc \_\_init\_\_
339 @section pipe_tasks_calibrate_IO Invoking the Task
341 If you want this task to unpersist inputs or persist outputs, then call
342 the `runDataRef` method (a wrapper around the `run` method).
344 If you already have the inputs unpersisted and do not want to persist the
345 output then it is more direct to call the `run` method:
347 @section pipe_tasks_calibrate_Config Configuration parameters
349 See @ref CalibrateConfig
351 @section pipe_tasks_calibrate_Metadata Quantities set in exposure Metadata
355 <dt>MAGZERO_RMS <dd>MAGZERO's RMS == sigma reported by photoCal task
356 <dt>MAGZERO_NOBJ <dd>Number of stars used == ngood reported by photoCal
358 <dt>COLORTERM1 <dd>?? (always 0.0)
359 <dt>COLORTERM2 <dd>?? (always 0.0)
360 <dt>COLORTERM3 <dd>?? (always 0.0)
363 @section pipe_tasks_calibrate_Debug Debug variables
365 The @link lsst.pipe.base.cmdLineTask.CmdLineTask command line task@endlink
366 interface supports a flag
367 `--debug` to import `debug.py` from your `$PYTHONPATH`; see @ref baseDebug
368 for more about `debug.py`.
370 CalibrateTask has a debug dictionary containing one key:
373 <dd>frame (an int; <= 0 to not display) in which to display the exposure,
374 sources and matches. See @ref lsst.meas.astrom.displayAstrometry for
375 the meaning of the various symbols.
378 For example, put something like:
382 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would
383 # call us recursively
384 if name == "lsst.pipe.tasks.calibrate":
391 lsstDebug.Info = DebugInfo
393 into your `debug.py` file and run `calibrateTask.py` with the `--debug`
396 Some subtasks may have their own debug variables; see individual Task
403 ConfigClass = CalibrateConfig
404 _DefaultName =
"calibrate"
405 RunnerClass = pipeBase.ButlerInitializedTaskRunner
407 def __init__(self, butler=None, astromRefObjLoader=None,
408 photoRefObjLoader=None, icSourceSchema=None,
409 initInputs=None, **kwargs):
410 """!Construct a CalibrateTask
412 @param[in] butler The butler is passed to the refObjLoader constructor
413 in case it is needed. Ignored if the refObjLoader argument
414 provides a loader directly.
415 @param[in] astromRefObjLoader An instance of LoadReferenceObjectsTasks
416 that supplies an external reference catalog for astrometric
417 calibration. May be None if the desired loader can be constructed
418 from the butler argument or all steps requiring a reference catalog
420 @param[in] photoRefObjLoader An instance of LoadReferenceObjectsTasks
421 that supplies an external reference catalog for photometric
422 calibration. May be None if the desired loader can be constructed
423 from the butler argument or all steps requiring a reference catalog
425 @param[in] icSourceSchema schema for icSource catalog, or None.
426 Schema values specified in config.icSourceFieldsToCopy will be
427 taken from this schema. If set to None, no values will be
428 propagated from the icSourceCatalog
429 @param[in,out] kwargs other keyword arguments for
430 lsst.pipe.base.CmdLineTask
434 if icSourceSchema
is None and butler
is not None:
436 icSourceSchema = butler.get(
"icSrc_schema", immediate=
True).schema
438 if icSourceSchema
is None and butler
is None and initInputs
is not None:
439 icSourceSchema = initInputs[
'icSourceSchema'].schema
441 if icSourceSchema
is not None:
443 self.
schemaMapper = afwTable.SchemaMapper(icSourceSchema)
444 minimumSchema = afwTable.SourceTable.makeMinimalSchema()
445 self.
schemaMapper.addMinimalSchema(minimumSchema,
False)
453 afwTable.Field[
"Flag"](
"calib_detected",
454 "Source was detected as an icSource"))
455 missingFieldNames = []
456 for fieldName
in self.config.icSourceFieldsToCopy:
458 schemaItem = icSourceSchema.find(fieldName)
460 missingFieldNames.append(fieldName)
465 if missingFieldNames:
466 raise RuntimeError(
"isSourceCat is missing fields {} "
467 "specified in icSourceFieldsToCopy"
468 .format(missingFieldNames))
475 self.
schema = afwTable.SourceTable.makeMinimalSchema()
476 self.makeSubtask(
'detection', schema=self.
schema)
483 if self.config.doInsertFakes:
484 self.makeSubtask(
"insertFakes")
486 if self.config.doDeblend:
487 self.makeSubtask(
"deblend", schema=self.
schema)
488 self.makeSubtask(
'measurement', schema=self.
schema,
490 if self.config.doApCorr:
491 self.makeSubtask(
'applyApCorr', schema=self.
schema)
492 self.makeSubtask(
'catalogCalculation', schema=self.
schema)
494 if self.config.doAstrometry:
495 if astromRefObjLoader
is None and butler
is not None:
496 self.makeSubtask(
'astromRefObjLoader', butler=butler)
497 astromRefObjLoader = self.astromRefObjLoader
498 self.
pixelMargin = astromRefObjLoader.config.pixelMargin
499 self.makeSubtask(
"astrometry", refObjLoader=astromRefObjLoader,
501 if self.config.doPhotoCal:
502 if photoRefObjLoader
is None and butler
is not None:
503 self.makeSubtask(
'photoRefObjLoader', butler=butler)
504 photoRefObjLoader = self.photoRefObjLoader
505 self.
pixelMargin = photoRefObjLoader.config.pixelMargin
506 self.makeSubtask(
"photoCal", refObjLoader=photoRefObjLoader,
509 if initInputs
is not None and (astromRefObjLoader
is not None or photoRefObjLoader
is not None):
510 raise RuntimeError(
"PipelineTask form of this task should not be initialized with "
511 "reference object loaders.")
516 self.
schema.checkUnits(parse_strict=self.config.checkUnitsParseStrict)
518 sourceCatSchema = afwTable.SourceCatalog(self.
schema)
523 def runDataRef(self, dataRef, exposure=None, background=None, icSourceCat=None,
525 """!Calibrate an exposure, optionally unpersisting inputs and
528 This is a wrapper around the `run` method that unpersists inputs
529 (if `doUnpersist` true) and persists outputs (if `config.doWrite` true)
531 @param[in] dataRef butler data reference corresponding to a science
533 @param[in,out] exposure characterized exposure (an
534 lsst.afw.image.ExposureF or similar), or None to unpersist existing
535 icExp and icBackground. See `run` method for details of what is
537 @param[in,out] background initial model of background already
538 subtracted from exposure (an lsst.afw.math.BackgroundList). May be
539 None if no background has been subtracted, though that is unusual
540 for calibration. A refined background model is output. Ignored if
542 @param[in] icSourceCat catalog from which to copy the fields specified
543 by icSourceKeys, or None;
544 @param[in] doUnpersist unpersist data:
545 - if True, exposure, background and icSourceCat are read from
546 dataRef and those three arguments must all be None;
547 - if False the exposure must be provided; background and
548 icSourceCat are optional. True is intended for running as a
549 command-line task, False for running as a subtask
550 @return same data as the calibrate method
552 self.log.info(
"Processing %s" % (dataRef.dataId))
555 if any(item
is not None for item
in (exposure, background,
557 raise RuntimeError(
"doUnpersist true; exposure, background "
558 "and icSourceCat must all be None")
559 exposure = dataRef.get(
"icExp", immediate=
True)
560 background = dataRef.get(
"icExpBackground", immediate=
True)
561 icSourceCat = dataRef.get(
"icSrc", immediate=
True)
562 elif exposure
is None:
563 raise RuntimeError(
"doUnpersist false; exposure must be provided")
565 exposureIdInfo = dataRef.get(
"expIdInfo")
569 exposureIdInfo=exposureIdInfo,
570 background=background,
571 icSourceCat=icSourceCat,
574 if self.config.doWrite:
577 exposure=calRes.exposure,
578 background=calRes.background,
579 sourceCat=calRes.sourceCat,
580 astromMatches=calRes.astromMatches,
581 matchMeta=calRes.matchMeta,
587 inputs = butlerQC.get(inputRefs)
588 expId, expBits = butlerQC.quantum.dataId.pack(
"visit_detector",
590 inputs[
'exposureIdInfo'] = ExposureIdInfo(expId, expBits)
592 if self.config.doAstrometry:
593 refObjLoader = ReferenceObjectLoader(dataIds=[ref.datasetRef.dataId
594 for ref
in inputRefs.astromRefCat],
595 refCats=inputs.pop(
'astromRefCat'),
596 config=self.config.astromRefObjLoader, log=self.log)
598 self.astrometry.setRefObjLoader(refObjLoader)
600 if self.config.doPhotoCal:
601 photoRefObjLoader = ReferenceObjectLoader(dataIds=[ref.datasetRef.dataId
602 for ref
in inputRefs.photoRefCat],
603 refCats=inputs.pop(
'photoRefCat'),
604 config=self.config.photoRefObjLoader,
606 self.
pixelMargin = photoRefObjLoader.config.pixelMargin
607 self.photoCal.match.setRefObjLoader(photoRefObjLoader)
609 outputs = self.
run(**inputs)
611 if self.config.doWriteMatches
and self.config.doAstrometry:
612 normalizedMatches = afwTable.packMatches(outputs.astromMatches)
613 normalizedMatches.table.setMetadata(outputs.matchMeta)
614 if self.config.doWriteMatchesDenormalized:
615 denormMatches = denormalizeMatches(outputs.astromMatches, outputs.matchMeta)
616 outputs.matchesDenormalized = denormMatches
617 outputs.matches = normalizedMatches
618 butlerQC.put(outputs, outputRefs)
620 def run(self, exposure, exposureIdInfo=None, background=None,
622 """!Calibrate an exposure (science image or coadd)
624 @param[in,out] exposure exposure to calibrate (an
625 lsst.afw.image.ExposureF or similar);
630 - MaskedImage has background subtracted
632 - PhotoCalib is replaced
633 @param[in] exposureIdInfo ID info for exposure (an
634 lsst.obs.base.ExposureIdInfo) If not provided, returned
635 SourceCatalog IDs will not be globally unique.
636 @param[in,out] background background model already subtracted from
637 exposure (an lsst.afw.math.BackgroundList). May be None if no
638 background has been subtracted, though that is unusual for
639 calibration. A refined background model is output.
640 @param[in] icSourceCat A SourceCatalog from CharacterizeImageTask
641 from which we can copy some fields.
643 @return pipe_base Struct containing these fields:
644 - exposure calibrate science exposure with refined WCS and PhotoCalib
645 - background model of background subtracted from exposure (an
646 lsst.afw.math.BackgroundList)
647 - sourceCat catalog of measured sources
648 - astromMatches list of source/refObj matches from the astrometry
652 if exposureIdInfo
is None:
653 exposureIdInfo = ExposureIdInfo()
655 if background
is None:
656 background = BackgroundList()
657 sourceIdFactory = IdFactory.makeSource(exposureIdInfo.expId,
658 exposureIdInfo.unusedBits)
659 table = SourceTable.make(self.
schema, sourceIdFactory)
662 detRes = self.detection.
run(table=table, exposure=exposure,
664 sourceCat = detRes.sources
665 if detRes.fpSets.background:
666 for bg
in detRes.fpSets.background:
667 background.append(bg)
668 if self.config.doDeblend:
669 self.deblend.
run(exposure=exposure, sources=sourceCat)
670 self.measurement.
run(
673 exposureId=exposureIdInfo.expId
675 if self.config.doApCorr:
676 self.applyApCorr.
run(
678 apCorrMap=exposure.getInfo().getApCorrMap()
680 self.catalogCalculation.
run(sourceCat)
682 if icSourceCat
is not None and \
683 len(self.config.icSourceFieldsToCopy) > 0:
691 if not sourceCat.isContiguous():
692 sourceCat = sourceCat.copy(deep=
True)
698 if self.config.doAstrometry:
700 astromRes = self.astrometry.
run(
704 astromMatches = astromRes.matches
705 matchMeta = astromRes.matchMeta
706 except Exception
as e:
707 if self.config.requireAstrometry:
709 self.log.warn(
"Unable to perform astrometric calibration "
710 "(%s): attempting to proceed" % e)
713 if self.config.doPhotoCal:
715 photoRes = self.photoCal.
run(exposure, sourceCat=sourceCat, expId=exposureIdInfo.expId)
716 exposure.setPhotoCalib(photoRes.photoCalib)
718 self.log.info(
"Photometric zero-point: %f" %
719 photoRes.photoCalib.instFluxToMagnitude(1.0))
720 self.
setMetadata(exposure=exposure, photoRes=photoRes)
721 except Exception
as e:
722 if self.config.requirePhotoCal:
724 self.log.warn(
"Unable to perform photometric calibration "
725 "(%s): attempting to proceed" % e)
728 if self.config.doInsertFakes:
729 self.insertFakes.
run(exposure, background=background)
731 table = SourceTable.make(self.
schema, sourceIdFactory)
734 detRes = self.detection.
run(table=table, exposure=exposure,
736 sourceCat = detRes.sources
737 if detRes.fpSets.background:
738 for bg
in detRes.fpSets.background:
739 background.append(bg)
740 if self.config.doDeblend:
741 self.deblend.
run(exposure=exposure, sources=sourceCat)
742 self.measurement.
run(
745 exposureId=exposureIdInfo.expId
747 if self.config.doApCorr:
748 self.applyApCorr.
run(
750 apCorrMap=exposure.getInfo().getApCorrMap()
752 self.catalogCalculation.
run(sourceCat)
754 if icSourceCat
is not None and len(self.config.icSourceFieldsToCopy) > 0:
758 frame = getDebugFrame(self._display,
"calibrate")
763 matches=astromMatches,
768 return pipeBase.Struct(
770 background=background,
772 astromMatches=astromMatches,
776 outputExposure=exposure,
778 outputBackground=background,
782 astromMatches, matchMeta):
783 """Write output data to the output repository
785 @param[in] dataRef butler data reference corresponding to a science
787 @param[in] exposure exposure to write
788 @param[in] background background model for exposure
789 @param[in] sourceCat catalog of measured sources
790 @param[in] astromMatches list of source/refObj matches from the
793 dataRef.put(sourceCat,
"src")
794 if self.config.doWriteMatches
and astromMatches
is not None:
795 normalizedMatches = afwTable.packMatches(astromMatches)
796 normalizedMatches.table.setMetadata(matchMeta)
797 dataRef.put(normalizedMatches,
"srcMatch")
798 if self.config.doWriteMatchesDenormalized:
799 denormMatches = denormalizeMatches(astromMatches, matchMeta)
800 dataRef.put(denormMatches,
"srcMatchFull")
801 if self.config.doWriteExposure:
802 dataRef.put(exposure,
"calexp")
803 dataRef.put(background,
"calexpBackground")
806 """Return a dict of empty catalogs for each catalog dataset produced
809 sourceCat = afwTable.SourceCatalog(self.
schema)
811 return {
"src": sourceCat}
814 """!Set task and exposure metadata
816 Logs a warning and continues if needed data is missing.
818 @param[in,out] exposure exposure whose metadata is to be set
819 @param[in] photoRes results of running photoCal; if None then it was
825 metadata = exposure.getMetadata()
829 exposureTime = exposure.getInfo().getVisitInfo().getExposureTime()
830 magZero = photoRes.zp - 2.5*math.log10(exposureTime)
832 self.log.warn(
"Could not set normalized MAGZERO in header: no "
837 metadata.set(
'MAGZERO', magZero)
838 metadata.set(
'MAGZERO_RMS', photoRes.sigma)
839 metadata.set(
'MAGZERO_NOBJ', photoRes.ngood)
840 metadata.set(
'COLORTERM1', 0.0)
841 metadata.set(
'COLORTERM2', 0.0)
842 metadata.set(
'COLORTERM3', 0.0)
843 except Exception
as e:
844 self.log.warn(
"Could not set exposure metadata: %s" % (e,))
847 """!Match sources in icSourceCat and sourceCat and copy the specified fields
849 @param[in] icSourceCat catalog from which to copy fields
850 @param[in,out] sourceCat catalog to which to copy fields
852 The fields copied are those specified by `config.icSourceFieldsToCopy`
853 that actually exist in the schema. This was set up by the constructor
854 using self.schemaMapper.
857 raise RuntimeError(
"To copy icSource fields you must specify "
858 "icSourceSchema nd icSourceKeys when "
859 "constructing this task")
860 if icSourceCat
is None or sourceCat
is None:
861 raise RuntimeError(
"icSourceCat and sourceCat must both be "
863 if len(self.config.icSourceFieldsToCopy) == 0:
864 self.log.warn(
"copyIcSourceFields doing nothing because "
865 "icSourceFieldsToCopy is empty")
868 mc = afwTable.MatchControl()
869 mc.findOnlyClosest =
False
870 matches = afwTable.matchXy(icSourceCat, sourceCat,
871 self.config.matchRadiusPix, mc)
872 if self.config.doDeblend:
873 deblendKey = sourceCat.schema[
"deblend_nChild"].asKey()
875 matches = [m
for m
in matches
if m[1].get(deblendKey) == 0]
882 for m0, m1, d
in matches:
884 match = bestMatches.get(id0)
885 if match
is None or d <= match[2]:
886 bestMatches[id0] = (m0, m1, d)
887 matches = list(bestMatches.values())
892 numMatches = len(matches)
893 numUniqueSources = len(set(m[1].getId()
for m
in matches))
894 if numUniqueSources != numMatches:
895 self.log.warn(
"{} icSourceCat sources matched only {} sourceCat "
896 "sources".format(numMatches, numUniqueSources))
898 self.log.info(
"Copying flags from icSourceCat to sourceCat for "
899 "%s sources" % (numMatches,))
903 for icSrc, src, d
in matches:
909 icSrcFootprint = icSrc.getFootprint()
911 icSrc.setFootprint(src.getFootprint())
914 icSrc.setFootprint(icSrcFootprint)