39import lsst.utils
as utils
41from .coaddBase
import CoaddBaseTask, SelectDataIdContainer, makeSkyInfo, makeCoaddSuffix, reorderAndPadList
42from .interpImage
import InterpImageTask
43from .scaleZeroPoint
import ScaleZeroPointTask
44from .coaddHelpers
import groupPatchExposures, getGroupDataRef
45from .scaleVariance
import ScaleVarianceTask
46from .maskStreaks
import MaskStreaksTask
47from .healSparseMapping
import HealSparseInputMapTask
49from lsst.daf.butler
import DeferredDatasetHandle
51__all__ = [
"AssembleCoaddTask",
"AssembleCoaddConnections",
"AssembleCoaddConfig",
52 "SafeClipAssembleCoaddTask",
"SafeClipAssembleCoaddConfig",
53 "CompareWarpAssembleCoaddTask",
"CompareWarpAssembleCoaddConfig"]
55log = logging.getLogger(__name__.partition(
".")[2])
59 dimensions=(
"tract",
"patch",
"band",
"skymap"),
60 defaultTemplates={
"inputCoaddName":
"deep",
61 "outputCoaddName":
"deep",
63 "warpTypeSuffix":
""}):
65 inputWarps = pipeBase.connectionTypes.Input(
66 doc=(
"Input list of warps to be assemebled i.e. stacked."
67 "WarpType (e.g. direct, psfMatched) is controlled by the warpType config parameter"),
68 name=
"{inputCoaddName}Coadd_{warpType}Warp",
69 storageClass=
"ExposureF",
70 dimensions=(
"tract",
"patch",
"skymap",
"visit",
"instrument"),
74 skyMap = pipeBase.connectionTypes.Input(
75 doc=
"Input definition of geometry/bbox and projection/wcs for coadded exposures",
76 name=BaseSkyMap.SKYMAP_DATASET_TYPE_NAME,
77 storageClass=
"SkyMap",
78 dimensions=(
"skymap", ),
80 selectedVisits = pipeBase.connectionTypes.Input(
81 doc=
"Selected visits to be coadded.",
82 name=
"{outputCoaddName}Visits",
83 storageClass=
"StructuredDataDict",
84 dimensions=(
"instrument",
"tract",
"patch",
"skymap",
"band")
86 brightObjectMask = pipeBase.connectionTypes.PrerequisiteInput(
87 doc=(
"Input Bright Object Mask mask produced with external catalogs to be applied to the mask plane"
89 name=
"brightObjectMask",
90 storageClass=
"ObjectMaskCatalog",
91 dimensions=(
"tract",
"patch",
"skymap",
"band"),
93 coaddExposure = pipeBase.connectionTypes.Output(
94 doc=
"Output coadded exposure, produced by stacking input warps",
95 name=
"{outputCoaddName}Coadd{warpTypeSuffix}",
96 storageClass=
"ExposureF",
97 dimensions=(
"tract",
"patch",
"skymap",
"band"),
99 nImage = pipeBase.connectionTypes.Output(
100 doc=
"Output image of number of input images per pixel",
101 name=
"{outputCoaddName}Coadd_nImage",
102 storageClass=
"ImageU",
103 dimensions=(
"tract",
"patch",
"skymap",
"band"),
105 inputMap = pipeBase.connectionTypes.Output(
106 doc=
"Output healsparse map of input images",
107 name=
"{outputCoaddName}Coadd_inputMap",
108 storageClass=
"HealSparseMap",
109 dimensions=(
"tract",
"patch",
"skymap",
"band"),
112 def __init__(self, *, config=None):
113 super().__init__(config=config)
118 templateValues = {name: getattr(config.connections, name)
for name
in self.defaultTemplates}
119 templateValues[
'warpType'] = config.warpType
120 templateValues[
'warpTypeSuffix'] = makeCoaddSuffix(config.warpType)
121 self._nameOverrides = {name: getattr(config.connections, name).format(**templateValues)
122 for name
in self.allConnections}
123 self._typeNameToVarName = {v: k
for k, v
in self._nameOverrides.items()}
126 if not config.doMaskBrightObjects:
127 self.prerequisiteInputs.remove(
"brightObjectMask")
129 if not config.doSelectVisits:
130 self.inputs.remove(
"selectedVisits")
132 if not config.doNImage:
133 self.outputs.remove(
"nImage")
135 if not self.config.doInputMap:
136 self.outputs.remove(
"inputMap")
139class AssembleCoaddConfig(CoaddBaseTask.ConfigClass, pipeBase.PipelineTaskConfig,
140 pipelineConnections=AssembleCoaddConnections):
141 """Configuration parameters for the `AssembleCoaddTask`.
145 The `doMaskBrightObjects` and `brightObjectMaskName` configuration options
146 only set the bitplane config.brightObjectMaskName. To make this useful you
147 *must* also configure the flags.pixel algorithm,
for example by adding
151 config.measurement.plugins[
"base_PixelFlags"].masksFpCenter.append(
"BRIGHT_OBJECT")
152 config.measurement.plugins[
"base_PixelFlags"].masksFpAnywhere.append(
"BRIGHT_OBJECT")
154 to your measureCoaddSources.py
and forcedPhotCoadd.py config overrides.
156 warpType = pexConfig.Field(
157 doc="Warp name: one of 'direct' or 'psfMatched'",
161 subregionSize = pexConfig.ListField(
163 doc=
"Width, height of stack subregion size; "
164 "make small enough that a full stack of images will fit into memory at once.",
166 default=(2000, 2000),
168 statistic = pexConfig.Field(
170 doc=
"Main stacking statistic for aggregating over the epochs.",
173 doOnlineForMean = pexConfig.Field(
175 doc=
"Perform online coaddition when statistic=\"MEAN\" to save memory?",
178 doSigmaClip = pexConfig.Field(
180 doc=
"Perform sigma clipped outlier rejection with MEANCLIP statistic? (DEPRECATED)",
183 sigmaClip = pexConfig.Field(
185 doc=
"Sigma for outlier rejection; ignored if non-clipping statistic selected.",
188 clipIter = pexConfig.Field(
190 doc=
"Number of iterations of outlier rejection; ignored if non-clipping statistic selected.",
193 calcErrorFromInputVariance = pexConfig.Field(
195 doc=
"Calculate coadd variance from input variance by stacking statistic."
196 "Passed to StatisticsControl.setCalcErrorFromInputVariance()",
199 scaleZeroPoint = pexConfig.ConfigurableField(
200 target=ScaleZeroPointTask,
201 doc=
"Task to adjust the photometric zero point of the coadd temp exposures",
203 doInterp = pexConfig.Field(
204 doc=
"Interpolate over NaN pixels? Also extrapolate, if necessary, but the results are ugly.",
208 interpImage = pexConfig.ConfigurableField(
209 target=InterpImageTask,
210 doc=
"Task to interpolate (and extrapolate) over NaN pixels",
212 doWrite = pexConfig.Field(
213 doc=
"Persist coadd?",
217 doNImage = pexConfig.Field(
218 doc=
"Create image of number of contributing exposures for each pixel",
222 doUsePsfMatchedPolygons = pexConfig.Field(
223 doc=
"Use ValidPolygons from shrunk Psf-Matched Calexps? Should be set to True by CompareWarp only.",
227 maskPropagationThresholds = pexConfig.DictField(
230 doc=(
"Threshold (in fractional weight) of rejection at which we propagate a mask plane to "
231 "the coadd; that is, we set the mask bit on the coadd if the fraction the rejected frames "
232 "would have contributed exceeds this value."),
233 default={
"SAT": 0.1},
235 removeMaskPlanes = pexConfig.ListField(dtype=str, default=[
"NOT_DEBLENDED"],
236 doc=
"Mask planes to remove before coadding")
237 doMaskBrightObjects = pexConfig.Field(dtype=bool, default=
False,
238 doc=
"Set mask and flag bits for bright objects?")
239 brightObjectMaskName = pexConfig.Field(dtype=str, default=
"BRIGHT_OBJECT",
240 doc=
"Name of mask bit used for bright objects")
241 coaddPsf = pexConfig.ConfigField(
242 doc=
"Configuration for CoaddPsf",
243 dtype=measAlg.CoaddPsfConfig,
245 doAttachTransmissionCurve = pexConfig.Field(
246 dtype=bool, default=
False, optional=
False,
247 doc=(
"Attach a piecewise TransmissionCurve for the coadd? "
248 "(requires all input Exposures to have TransmissionCurves).")
250 hasFakes = pexConfig.Field(
253 doc=
"Should be set to True if fake sources have been inserted into the input data."
255 doSelectVisits = pexConfig.Field(
256 doc=
"Coadd only visits selected by a SelectVisitsTask",
260 doInputMap = pexConfig.Field(
261 doc=
"Create a bitwise map of coadd inputs",
265 inputMapper = pexConfig.ConfigurableField(
266 doc=
"Input map creation subtask.",
267 target=HealSparseInputMapTask,
270 def setDefaults(self):
271 super().setDefaults()
272 self.badMaskPlanes = [
"NO_DATA",
"BAD",
"SAT",
"EDGE"]
279 log.warning(
"Config doPsfMatch deprecated. Setting warpType='psfMatched'")
280 self.warpType =
'psfMatched'
281 if self.doSigmaClip
and self.statistic !=
"MEANCLIP":
282 log.warning(
'doSigmaClip deprecated. To replicate behavior, setting statistic to "MEANCLIP"')
283 self.statistic =
"MEANCLIP"
284 if self.doInterp
and self.statistic
not in [
'MEAN',
'MEDIAN',
'MEANCLIP',
'VARIANCE',
'VARIANCECLIP']:
285 raise ValueError(
"Must set doInterp=False for statistic=%s, which does not "
286 "compute and set a non-zero coadd variance estimate." % (self.statistic))
288 unstackableStats = [
'NOTHING',
'ERROR',
'ORMASK']
289 if not hasattr(afwMath.Property, self.statistic)
or self.statistic
in unstackableStats:
290 stackableStats = [str(k)
for k
in afwMath.Property.__members__.keys()
291 if str(k)
not in unstackableStats]
292 raise ValueError(
"statistic %s is not allowed. Please choose one of %s."
293 % (self.statistic, stackableStats))
296class AssembleCoaddTask(
CoaddBaseTask, pipeBase.PipelineTask):
297 """Assemble a coadded image from a set of warps (coadded temporary exposures).
299 We want to assemble a coadded image from a set of Warps (also called
300 coadded temporary exposures
or ``coaddTempExps``).
301 Each input Warp covers a patch on the sky
and corresponds to a single
302 run/visit/exposure of the covered patch. We provide the task
with a list
303 of Warps (``selectDataList``)
from which it selects Warps that cover the
304 specified patch (pointed at by ``dataRef``).
305 Each Warp that goes into a coadd will typically have an independent
306 photometric zero-point. Therefore, we must scale each Warp to set it to
307 a common photometric zeropoint. WarpType may be one of
'direct' or
308 'psfMatched',
and the boolean configs `config.makeDirect`
and
309 `config.makePsfMatched` set which of the warp types will be coadded.
310 The coadd
is computed
as a mean
with optional outlier rejection.
311 Criteria
for outlier rejection are set
in `AssembleCoaddConfig`.
312 Finally, Warps can have bad
'NaN' pixels which received no input
from the
313 source calExps. We interpolate over these bad (NaN) pixels.
315 `AssembleCoaddTask` uses several sub-tasks. These are
317 - `ScaleZeroPointTask`
318 - create
and use an ``imageScaler`` object to scale the photometric zeropoint
for each Warp
320 - interpolate across bad pixels (NaN)
in the final coadd
322 You can retarget these subtasks
if you wish.
326 The `lsst.pipe.base.cmdLineTask.CmdLineTask` interface supports a
327 flag ``-d`` to
import ``debug.py``
from your ``PYTHONPATH``; see
328 `baseDebug`
for more about ``debug.py`` files. `AssembleCoaddTask` has
329 no debug variables of its own. Some of the subtasks may support debug
330 variables. See the documentation
for the subtasks
for further information.
334 `AssembleCoaddTask` assembles a set of warped images into a coadded image.
335 The `AssembleCoaddTask` can be invoked by running ``assembleCoadd.py``
336 with the flag
'--legacyCoadd'. Usage of assembleCoadd.py expects two
337 inputs: a data reference to the tract patch
and filter to be coadded,
and
338 a list of Warps to attempt to coadd. These are specified using ``--id``
and
339 ``--selectId``, respectively:
343 --id = [KEY=VALUE1[^VALUE2[^VALUE3...] [KEY=VALUE1[^VALUE2[^VALUE3...] ...]]
344 --selectId [KEY=VALUE1[^VALUE2[^VALUE3...] [KEY=VALUE1[^VALUE2[^VALUE3...] ...]]
346 Only the Warps that cover the specified tract
and patch will be coadded.
347 A list of the available optional arguments can be obtained by calling
348 ``assembleCoadd.py``
with the ``--help`` command line argument:
352 assembleCoadd.py --help
354 To demonstrate usage of the `AssembleCoaddTask`
in the larger context of
355 multi-band processing, we will generate the HSC-I & -R band coadds
from
356 HSC engineering test data provided
in the ``ci_hsc`` package. To begin,
357 assuming that the lsst stack has been already set up, we must set up the
358 obs_subaru
and ``ci_hsc`` packages. This defines the environment variable
359 ``$CI_HSC_DIR``
and points at the location of the package. The raw HSC
360 data live
in the ``$CI_HSC_DIR/raw directory``. To begin assembling the
361 coadds, we must first
364 - process the individual ccds
in $CI_HSC_RAW to produce calibrated exposures
366 - create a skymap that covers the area of the sky present
in the raw exposures
368 - warp the individual calibrated exposures to the tangent plane of the coadd
370 We can perform all of these steps by running
374 $CI_HSC_DIR scons warp-903986 warp-904014 warp-903990 warp-904010 warp-903988
376 This will produce warped exposures
for each visit. To coadd the warped
377 data, we call assembleCoadd.py
as follows:
381 assembleCoadd.py --legacyCoadd $CI_HSC_DIR/DATA --id patch=5,4 tract=0 filter=HSC-I \
382 --selectId visit=903986 ccd=16 --selectId visit=903986 ccd=22 --selectId visit=903986 ccd=23 \
383 --selectId visit=903986 ccd=100 --selectId visit=904014 ccd=1 --selectId visit=904014 ccd=6 \
384 --selectId visit=904014 ccd=12 --selectId visit=903990 ccd=18 --selectId visit=903990 ccd=25 \
385 --selectId visit=904010 ccd=4 --selectId visit=904010 ccd=10 --selectId visit=904010 ccd=100 \
386 --selectId visit=903988 ccd=16 --selectId visit=903988 ccd=17 --selectId visit=903988 ccd=23 \
387 --selectId visit=903988 ccd=24
389 that will process the HSC-I band data. The results are written
in
390 ``$CI_HSC_DIR/DATA/deepCoadd-results/HSC-I``.
392 You may also choose to run:
396 scons warp-903334 warp-903336 warp-903338 warp-903342 warp-903344 warp-903346
397 assembleCoadd.py --legacyCoadd $CI_HSC_DIR/DATA --id patch=5,4 tract=0 filter=HSC-R \
398 --selectId visit=903334 ccd=16 --selectId visit=903334 ccd=22 --selectId visit=903334 ccd=23 \
399 --selectId visit=903334 ccd=100 --selectId visit=903336 ccd=17 --selectId visit=903336 ccd=24 \
400 --selectId visit=903338 ccd=18 --selectId visit=903338 ccd=25 --selectId visit=903342 ccd=4 \
401 --selectId visit=903342 ccd=10 --selectId visit=903342 ccd=100 --selectId visit=903344 ccd=0 \
402 --selectId visit=903344 ccd=5 --selectId visit=903344 ccd=11 --selectId visit=903346 ccd=1 \
403 --selectId visit=903346 ccd=6 --selectId visit=903346 ccd=12
405 to generate the coadd
for the HSC-R band
if you are interested
in
406 following multiBand Coadd processing
as discussed
in `pipeTasks_multiBand`
407 (but note that normally, one would use the `SafeClipAssembleCoaddTask`
408 rather than `AssembleCoaddTask` to make the coadd.
410 ConfigClass = AssembleCoaddConfig
411 _DefaultName = "assembleCoadd"
413 def __init__(self, *args, **kwargs):
416 argNames = [
"config",
"name",
"parentTask",
"log"]
417 kwargs.update({k: v
for k, v
in zip(argNames, args)})
418 warnings.warn(
"AssembleCoadd received positional args, and casting them as kwargs: %s. "
419 "PipelineTask will not take positional args" % argNames, FutureWarning)
421 super().__init__(**kwargs)
422 self.makeSubtask(
"interpImage")
423 self.makeSubtask(
"scaleZeroPoint")
425 if self.config.doMaskBrightObjects:
426 mask = afwImage.Mask()
428 self.brightObjectBitmask = 1 << mask.addMaskPlane(self.config.brightObjectMaskName)
429 except pexExceptions.LsstCppException:
430 raise RuntimeError(
"Unable to define mask plane for bright objects; planes used are %s" %
431 mask.getMaskPlaneDict().keys())
434 if self.config.doInputMap:
435 self.makeSubtask(
"inputMapper")
437 self.warpType = self.config.warpType
439 @utils.inheritDoc(pipeBase.PipelineTask)
440 def runQuantum(self, butlerQC, inputRefs, outputRefs):
445 Assemble a coadd from a set of Warps.
447 PipelineTask (Gen3) entry point to Coadd a set of Warps.
448 Analogous to `runDataRef`, it prepares all the data products to be
449 passed to `run`,
and processes the results before returning a struct
450 of results to be written out. AssembleCoadd cannot fit all Warps
in memory.
451 Therefore, its inputs are accessed subregion by subregion
452 by the Gen3 `DeferredDatasetHandle` that
is analagous to the Gen2
453 `lsst.daf.persistence.ButlerDataRef`. Any updates to this method should
454 correspond to an update
in `runDataRef`
while both entry points
457 inputData = butlerQC.get(inputRefs)
461 skyMap = inputData[
"skyMap"]
462 outputDataId = butlerQC.quantum.dataId
464 inputData[
'skyInfo'] = makeSkyInfo(skyMap,
465 tractId=outputDataId[
'tract'],
466 patchId=outputDataId[
'patch'])
468 if self.config.doSelectVisits:
469 warpRefList = self.filterWarps(inputData[
'inputWarps'], inputData[
'selectedVisits'])
471 warpRefList = inputData[
'inputWarps']
474 inputs = self.prepareInputs(warpRefList)
475 self.log.info(
"Found %d %s", len(inputs.tempExpRefList),
476 self.getTempExpDatasetName(self.warpType))
477 if len(inputs.tempExpRefList) == 0:
478 raise pipeBase.NoWorkFound(
"No coadd temporary exposures found")
480 supplementaryData = self.makeSupplementaryDataGen3(butlerQC, inputRefs, outputRefs)
481 retStruct = self.run(inputData[
'skyInfo'], inputs.tempExpRefList, inputs.imageScalerList,
482 inputs.weightList, supplementaryData=supplementaryData)
484 inputData.setdefault(
'brightObjectMask',
None)
485 self.processResults(retStruct.coaddExposure, inputData[
'brightObjectMask'], outputDataId)
487 if self.config.doWrite:
488 butlerQC.put(retStruct, outputRefs)
492 def runDataRef(self, dataRef, selectDataList=None, warpRefList=None):
493 """Assemble a coadd from a set of Warps.
495 Pipebase.CmdlineTask entry point to Coadd a set of Warps.
496 Compute weights to be applied to each Warp and
497 find scalings to match the photometric zeropoint to a reference Warp.
498 Assemble the Warps using `run`. Interpolate over NaNs
and
499 optionally write the coadd to disk. Return the coadded exposure.
504 Data reference defining the patch
for coaddition
and the
505 reference Warp (
if ``config.autoReference=
False``).
506 Used to access the following data products:
507 - ``self.config.coaddName +
"Coadd_skyMap"``
508 - ``self.config.coaddName +
"Coadd_ + <warpType> + "Warp
"`` (optionally)
509 - ``self.config.coaddName + "Coadd"``
510 selectDataList : `list`
511 List of data references to Calexps. Data to be coadded will be
512 selected
from this list based on overlap
with the patch defined
513 by dataRef, grouped by visit,
and converted to a list of data
516 List of data references to Warps to be coadded.
517 Note: `warpRefList`
is just the new name
for `tempExpRefList`.
521 retStruct : `lsst.pipe.base.Struct`
522 Result struct
with components:
524 - ``coaddExposure``: coadded exposure (``Exposure``).
525 - ``nImage``: exposure count image (``Image``).
527 if selectDataList
and warpRefList:
528 raise RuntimeError(
"runDataRef received both a selectDataList and warpRefList, "
529 "and which to use is ambiguous. Please pass only one.")
531 skyInfo = self.getSkyInfo(dataRef)
532 if warpRefList
is None:
533 calExpRefList = self.selectExposures(dataRef, skyInfo, selectDataList=selectDataList)
534 if len(calExpRefList) == 0:
535 self.log.warning(
"No exposures to coadd")
537 self.log.info(
"Coadding %d exposures", len(calExpRefList))
539 warpRefList = self.getTempExpRefList(dataRef, calExpRefList)
541 inputData = self.prepareInputs(warpRefList)
542 self.log.info(
"Found %d %s", len(inputData.tempExpRefList),
543 self.getTempExpDatasetName(self.warpType))
544 if len(inputData.tempExpRefList) == 0:
545 self.log.warning(
"No coadd temporary exposures found")
548 supplementaryData = self.makeSupplementaryData(dataRef, warpRefList=inputData.tempExpRefList)
550 retStruct = self.run(skyInfo, inputData.tempExpRefList, inputData.imageScalerList,
551 inputData.weightList, supplementaryData=supplementaryData)
553 brightObjects = self.readBrightObjectMasks(dataRef)
if self.config.doMaskBrightObjects
else None
554 self.processResults(retStruct.coaddExposure, brightObjectMasks=brightObjects, dataId=dataRef.dataId)
556 if self.config.doWrite:
557 if self.getCoaddDatasetName(self.warpType) ==
"deepCoadd" and self.config.hasFakes:
558 coaddDatasetName =
"fakes_" + self.getCoaddDatasetName(self.warpType)
560 coaddDatasetName = self.getCoaddDatasetName(self.warpType)
561 self.log.info(
"Persisting %s", coaddDatasetName)
562 dataRef.put(retStruct.coaddExposure, coaddDatasetName)
563 if self.config.doNImage
and retStruct.nImage
is not None:
564 dataRef.put(retStruct.nImage, self.getCoaddDatasetName(self.warpType) +
'_nImage')
569 """Interpolate over missing data and mask bright stars.
574 The coadded exposure to process.
575 dataRef : `lsst.daf.persistence.ButlerDataRef`
576 Butler data reference for supplementary data.
578 if self.config.doInterp:
579 self.interpImage.
run(coaddExposure.getMaskedImage(), planeName=
"NO_DATA")
581 varArray = coaddExposure.variance.array
582 with numpy.errstate(invalid=
"ignore"):
583 varArray[:] = numpy.where(varArray > 0, varArray, numpy.inf)
585 if self.config.doMaskBrightObjects:
586 self.setBrightObjectMasks(coaddExposure, brightObjectMasks, dataId)
589 """Make additional inputs to run() specific to subclasses (Gen2)
591 Duplicates interface of `runDataRef` method
592 Available to be implemented by subclasses only if they need the
593 coadd dataRef
for performing preliminary processing before
594 assembling the coadd.
598 dataRef : `lsst.daf.persistence.ButlerDataRef`
599 Butler data reference
for supplementary data.
600 selectDataList : `list` (optional)
601 Optional List of data references to Calexps.
602 warpRefList : `list` (optional)
603 Optional List of data references to Warps.
605 return pipeBase.Struct()
608 """Make additional inputs to run() specific to subclasses (Gen3)
610 Duplicates interface of `runQuantum` method.
611 Available to be implemented by subclasses only if they need the
612 coadd dataRef
for performing preliminary processing before
613 assembling the coadd.
617 butlerQC : `lsst.pipe.base.ButlerQuantumContext`
618 Gen3 Butler object
for fetching additional data products before
619 running the Task specialized
for quantum being processed
620 inputRefs : `lsst.pipe.base.InputQuantizedConnection`
621 Attributes are the names of the connections describing input dataset types.
622 Values are DatasetRefs that task consumes
for corresponding dataset type.
623 DataIds are guaranteed to match data objects
in ``inputData``.
624 outputRefs : `lsst.pipe.base.OutputQuantizedConnection`
625 Attributes are the names of the connections describing output dataset types.
626 Values are DatasetRefs that task
is to produce
627 for corresponding dataset type.
629 return pipeBase.Struct()
632 """Generate list data references corresponding to warped exposures
633 that lie within the patch to be coadded.
638 Data reference for patch.
639 calExpRefList : `list`
640 List of data references
for input calexps.
644 tempExpRefList : `list`
645 List of Warp/CoaddTempExp data references.
647 butler = patchRef.getButler()
648 groupData = groupPatchExposures(patchRef, calExpRefList, self.getCoaddDatasetName(self.warpType),
649 self.getTempExpDatasetName(self.warpType))
650 tempExpRefList = [getGroupDataRef(butler, self.getTempExpDatasetName(self.warpType),
651 g, groupData.keys) for
652 g
in groupData.groups.keys()]
653 return tempExpRefList
656 """Prepare the input warps for coaddition by measuring the weight for
657 each warp and the scaling
for the photometric zero point.
659 Each Warp has its own photometric zeropoint
and background variance.
660 Before coadding these Warps together, compute a scale factor to
661 normalize the photometric zeropoint
and compute the weight
for each Warp.
666 List of data references to tempExp
670 result : `lsst.pipe.base.Struct`
671 Result struct
with components:
673 - ``tempExprefList``: `list` of data references to tempExp.
674 - ``weightList``: `list` of weightings.
675 - ``imageScalerList``: `list` of image scalers.
677 statsCtrl = afwMath.StatisticsControl()
678 statsCtrl.setNumSigmaClip(self.config.sigmaClip)
679 statsCtrl.setNumIter(self.config.clipIter)
680 statsCtrl.setAndMask(self.getBadPixelMask())
681 statsCtrl.setNanSafe(True)
688 tempExpName = self.getTempExpDatasetName(self.warpType)
689 for tempExpRef
in refList:
692 if not isinstance(tempExpRef, DeferredDatasetHandle):
693 if not tempExpRef.datasetExists(tempExpName):
694 self.log.warning(
"Could not find %s %s; skipping it", tempExpName, tempExpRef.dataId)
697 tempExp = tempExpRef.get(datasetType=tempExpName, immediate=
True)
699 if numpy.isnan(tempExp.image.array).all():
701 maskedImage = tempExp.getMaskedImage()
702 imageScaler = self.scaleZeroPoint.computeImageScaler(
707 imageScaler.scaleMaskedImage(maskedImage)
708 except Exception
as e:
709 self.log.warning(
"Scaling failed for %s (skipping it): %s", tempExpRef.dataId, e)
711 statObj = afwMath.makeStatistics(maskedImage.getVariance(), maskedImage.getMask(),
712 afwMath.MEANCLIP, statsCtrl)
713 meanVar, meanVarErr = statObj.getResult(afwMath.MEANCLIP)
714 weight = 1.0 / float(meanVar)
715 if not numpy.isfinite(weight):
716 self.log.warning(
"Non-finite weight for %s: skipping", tempExpRef.dataId)
718 self.log.info(
"Weight of %s %s = %0.3f", tempExpName, tempExpRef.dataId, weight)
723 tempExpRefList.append(tempExpRef)
724 weightList.append(weight)
725 imageScalerList.append(imageScaler)
727 return pipeBase.Struct(tempExpRefList=tempExpRefList, weightList=weightList,
728 imageScalerList=imageScalerList)
731 """Prepare the statistics for coadding images.
735 mask : `int`, optional
736 Bit mask value to exclude from coaddition.
740 stats : `lsst.pipe.base.Struct`
741 Statistics structure
with the following fields:
743 - ``statsCtrl``: Statistics control object
for coadd
745 - ``statsFlags``: Statistic
for coadd (`lsst.afw.math.Property`)
748 mask = self.getBadPixelMask()
749 statsCtrl = afwMath.StatisticsControl()
750 statsCtrl.setNumSigmaClip(self.config.sigmaClip)
751 statsCtrl.setNumIter(self.config.clipIter)
752 statsCtrl.setAndMask(mask)
753 statsCtrl.setNanSafe(
True)
754 statsCtrl.setWeighted(
True)
755 statsCtrl.setCalcErrorFromInputVariance(self.config.calcErrorFromInputVariance)
756 for plane, threshold
in self.config.maskPropagationThresholds.items():
757 bit = afwImage.Mask.getMaskPlane(plane)
758 statsCtrl.setMaskPropagationThreshold(bit, threshold)
759 statsFlags = afwMath.stringToStatisticsProperty(self.config.statistic)
760 return pipeBase.Struct(ctrl=statsCtrl, flags=statsFlags)
763 def run(self, skyInfo, tempExpRefList, imageScalerList, weightList,
764 altMaskList=None, mask=None, supplementaryData=None):
765 """Assemble a coadd from input warps
767 Assemble the coadd using the provided list of coaddTempExps. Since
768 the full coadd covers a patch (a large area), the assembly is
769 performed over small areas on the image at a time
in order to
770 conserve memory usage. Iterate over subregions within the outer
771 bbox of the patch using `assembleSubregion` to stack the corresponding
772 subregions
from the coaddTempExps
with the statistic specified.
773 Set the edge bits the coadd mask based on the weight map.
777 skyInfo : `lsst.pipe.base.Struct`
778 Struct
with geometric information about the patch.
779 tempExpRefList : `list`
780 List of data references to Warps (previously called CoaddTempExps).
781 imageScalerList : `list`
782 List of image scalers.
785 altMaskList : `list`, optional
786 List of alternate masks to use rather than those stored
with
788 mask : `int`, optional
789 Bit mask value to exclude
from coaddition.
790 supplementaryData : lsst.pipe.base.Struct, optional
791 Struct
with additional data products needed to assemble coadd.
792 Only used by subclasses that implement `makeSupplementaryData`
797 result : `lsst.pipe.base.Struct`
798 Result struct
with components:
802 - ``inputMap``: bit-wise map of inputs,
if requested.
803 - ``warpRefList``: input list of refs to the warps (
804 ``lsst.daf.butler.DeferredDatasetHandle``
or
805 ``lsst.daf.persistence.ButlerDataRef``)
807 - ``imageScalerList``: input list of image scalers (unmodified)
808 - ``weightList``: input list of weights (unmodified)
810 tempExpName = self.getTempExpDatasetName(self.warpType)
811 self.log.info("Assembling %s %s", len(tempExpRefList), tempExpName)
812 stats = self.prepareStats(mask=mask)
814 if altMaskList
is None:
815 altMaskList = [
None]*len(tempExpRefList)
817 coaddExposure = afwImage.ExposureF(skyInfo.bbox, skyInfo.wcs)
818 coaddExposure.setPhotoCalib(self.scaleZeroPoint.getPhotoCalib())
819 coaddExposure.getInfo().setCoaddInputs(self.inputRecorder.makeCoaddInputs())
820 self.assembleMetadata(coaddExposure, tempExpRefList, weightList)
821 coaddMaskedImage = coaddExposure.getMaskedImage()
822 subregionSizeArr = self.config.subregionSize
823 subregionSize =
geom.Extent2I(subregionSizeArr[0], subregionSizeArr[1])
825 if self.config.doNImage:
826 nImage = afwImage.ImageU(skyInfo.bbox)
831 if self.config.doInputMap:
832 self.inputMapper.build_ccd_input_map(skyInfo.bbox,
834 coaddExposure.getInfo().getCoaddInputs().ccds)
836 if self.config.doOnlineForMean
and self.config.statistic ==
"MEAN":
838 self.assembleOnlineMeanCoadd(coaddExposure, tempExpRefList, imageScalerList,
839 weightList, altMaskList, stats.ctrl,
841 except Exception
as e:
842 self.log.exception(
"Cannot compute online coadd %s", e)
845 for subBBox
in self._subBBoxIter(skyInfo.bbox, subregionSize):
847 self.assembleSubregion(coaddExposure, subBBox, tempExpRefList, imageScalerList,
848 weightList, altMaskList, stats.flags, stats.ctrl,
850 except Exception
as e:
851 self.log.exception(
"Cannot compute coadd %s: %s", subBBox, e)
855 if self.config.doInputMap:
856 self.inputMapper.finalize_ccd_input_map_mask()
857 inputMap = self.inputMapper.ccd_input_map
861 self.setInexactPsf(coaddMaskedImage.getMask())
864 coaddUtils.setCoaddEdgeBits(coaddMaskedImage.getMask(), coaddMaskedImage.getVariance())
865 return pipeBase.Struct(coaddExposure=coaddExposure, nImage=nImage,
866 warpRefList=tempExpRefList, imageScalerList=imageScalerList,
867 weightList=weightList, inputMap=inputMap)
870 """Set the metadata for the coadd.
872 This basic implementation sets the filter from the first input.
877 The target exposure
for the coadd.
878 tempExpRefList : `list`
879 List of data references to tempExp.
883 assert len(tempExpRefList) == len(weightList),
"Length mismatch"
884 tempExpName = self.getTempExpDatasetName(self.warpType)
890 if isinstance(tempExpRefList[0], DeferredDatasetHandle):
892 tempExpList = [tempExpRef.get(parameters={
'bbox': bbox})
for tempExpRef
in tempExpRefList]
895 tempExpList = [tempExpRef.get(tempExpName +
"_sub", bbox=bbox, immediate=
True)
896 for tempExpRef
in tempExpRefList]
897 numCcds = sum(len(tempExp.getInfo().getCoaddInputs().ccds)
for tempExp
in tempExpList)
901 coaddExposure.setFilterLabel(afwImage.FilterLabel(tempExpList[0].getFilterLabel().bandLabel))
902 coaddInputs = coaddExposure.getInfo().getCoaddInputs()
903 coaddInputs.ccds.reserve(numCcds)
904 coaddInputs.visits.reserve(len(tempExpList))
906 for tempExp, weight
in zip(tempExpList, weightList):
907 self.inputRecorder.addVisitToCoadd(coaddInputs, tempExp, weight)
909 if self.config.doUsePsfMatchedPolygons:
910 self.shrinkValidPolygons(coaddInputs)
912 coaddInputs.visits.sort()
913 if self.warpType ==
"psfMatched":
918 modelPsfList = [tempExp.getPsf()
for tempExp
in tempExpList]
919 modelPsfWidthList = [modelPsf.computeBBox().getWidth()
for modelPsf
in modelPsfList]
920 psf = modelPsfList[modelPsfWidthList.index(max(modelPsfWidthList))]
922 psf = measAlg.CoaddPsf(coaddInputs.ccds, coaddExposure.getWcs(),
923 self.config.coaddPsf.makeControl())
924 coaddExposure.setPsf(psf)
925 apCorrMap = measAlg.makeCoaddApCorrMap(coaddInputs.ccds, coaddExposure.getBBox(afwImage.PARENT),
926 coaddExposure.getWcs())
927 coaddExposure.getInfo().setApCorrMap(apCorrMap)
928 if self.config.doAttachTransmissionCurve:
929 transmissionCurve = measAlg.makeCoaddTransmissionCurve(coaddExposure.getWcs(), coaddInputs.ccds)
930 coaddExposure.getInfo().setTransmissionCurve(transmissionCurve)
933 altMaskList, statsFlags, statsCtrl, nImage=None):
934 """Assemble the coadd for a sub-region.
936 For each coaddTempExp, check for (
and swap
in) an alternative mask
937 if one
is passed. Remove mask planes listed
in
938 `config.removeMaskPlanes`. Finally, stack the actual exposures using
939 `lsst.afw.math.statisticsStack`
with the statistic specified by
940 statsFlags. Typically, the statsFlag will be one of lsst.afw.math.MEAN
for
941 a mean-stack
or `lsst.afw.math.MEANCLIP`
for outlier rejection using
942 an N-sigma clipped mean where N
and iterations are specified by
943 statsCtrl. Assign the stacked subregion back to the coadd.
948 The target exposure
for the coadd.
949 bbox : `lsst.geom.Box`
951 tempExpRefList : `list`
952 List of data reference to tempExp.
953 imageScalerList : `list`
954 List of image scalers.
958 List of alternate masks to use rather than those stored
with
959 tempExp,
or None. Each element
is dict
with keys = mask plane
960 name to which to add the spans.
961 statsFlags : `lsst.afw.math.Property`
962 Property object
for statistic
for coadd.
964 Statistics control object
for coadd.
965 nImage : `lsst.afw.image.ImageU`, optional
966 Keeps track of exposure count
for each pixel.
968 self.log.debug("Computing coadd over %s", bbox)
969 tempExpName = self.getTempExpDatasetName(self.warpType)
970 coaddExposure.mask.addMaskPlane(
"REJECTED")
971 coaddExposure.mask.addMaskPlane(
"CLIPPED")
972 coaddExposure.mask.addMaskPlane(
"SENSOR_EDGE")
973 maskMap = self.setRejectedMaskMapping(statsCtrl)
974 clipped = afwImage.Mask.getPlaneBitMask(
"CLIPPED")
976 if nImage
is not None:
977 subNImage = afwImage.ImageU(bbox.getWidth(), bbox.getHeight())
978 for tempExpRef, imageScaler, altMask
in zip(tempExpRefList, imageScalerList, altMaskList):
980 if isinstance(tempExpRef, DeferredDatasetHandle):
982 exposure = tempExpRef.get(parameters={
'bbox': bbox})
985 exposure = tempExpRef.get(tempExpName +
"_sub", bbox=bbox)
987 maskedImage = exposure.getMaskedImage()
988 mask = maskedImage.getMask()
989 if altMask
is not None:
990 self.applyAltMaskPlanes(mask, altMask)
991 imageScaler.scaleMaskedImage(maskedImage)
995 if nImage
is not None:
996 subNImage.getArray()[maskedImage.getMask().getArray() & statsCtrl.getAndMask() == 0] += 1
997 if self.config.removeMaskPlanes:
998 self.removeMaskPlanes(maskedImage)
999 maskedImageList.append(maskedImage)
1001 if self.config.doInputMap:
1002 visit = exposure.getInfo().getCoaddInputs().visits[0].getId()
1003 self.inputMapper.mask_warp_bbox(bbox, visit, mask, statsCtrl.getAndMask())
1005 with self.timer(
"stack"):
1006 coaddSubregion = afwMath.statisticsStack(maskedImageList, statsFlags, statsCtrl, weightList,
1009 coaddExposure.maskedImage.assign(coaddSubregion, bbox)
1010 if nImage
is not None:
1011 nImage.assign(subNImage, bbox)
1014 altMaskList, statsCtrl, nImage=None):
1015 """Assemble the coadd using the "online" method.
1017 This method takes a running sum of images and weights to save memory.
1018 It only works
for MEAN statistics.
1023 The target exposure
for the coadd.
1024 tempExpRefList : `list`
1025 List of data reference to tempExp.
1026 imageScalerList : `list`
1027 List of image scalers.
1030 altMaskList : `list`
1031 List of alternate masks to use rather than those stored
with
1032 tempExp,
or None. Each element
is dict
with keys = mask plane
1033 name to which to add the spans.
1035 Statistics control object
for coadd
1036 nImage : `lsst.afw.image.ImageU`, optional
1037 Keeps track of exposure count
for each pixel.
1039 self.log.debug("Computing online coadd.")
1040 tempExpName = self.getTempExpDatasetName(self.warpType)
1041 coaddExposure.mask.addMaskPlane(
"REJECTED")
1042 coaddExposure.mask.addMaskPlane(
"CLIPPED")
1043 coaddExposure.mask.addMaskPlane(
"SENSOR_EDGE")
1044 maskMap = self.setRejectedMaskMapping(statsCtrl)
1045 thresholdDict = AccumulatorMeanStack.stats_ctrl_to_threshold_dict(statsCtrl)
1047 bbox = coaddExposure.maskedImage.getBBox()
1049 stacker = AccumulatorMeanStack(
1050 coaddExposure.image.array.shape,
1051 statsCtrl.getAndMask(),
1052 mask_threshold_dict=thresholdDict,
1054 no_good_pixels_mask=statsCtrl.getNoGoodPixelsMask(),
1055 calc_error_from_input_variance=self.config.calcErrorFromInputVariance,
1056 compute_n_image=(nImage
is not None)
1059 for tempExpRef, imageScaler, altMask, weight
in zip(tempExpRefList,
1063 if isinstance(tempExpRef, DeferredDatasetHandle):
1065 exposure = tempExpRef.get()
1068 exposure = tempExpRef.get(tempExpName)
1070 maskedImage = exposure.getMaskedImage()
1071 mask = maskedImage.getMask()
1072 if altMask
is not None:
1073 self.applyAltMaskPlanes(mask, altMask)
1074 imageScaler.scaleMaskedImage(maskedImage)
1075 if self.config.removeMaskPlanes:
1076 self.removeMaskPlanes(maskedImage)
1078 stacker.add_masked_image(maskedImage, weight=weight)
1080 if self.config.doInputMap:
1081 visit = exposure.getInfo().getCoaddInputs().visits[0].getId()
1082 self.inputMapper.mask_warp_bbox(bbox, visit, mask, statsCtrl.getAndMask())
1084 stacker.fill_stacked_masked_image(coaddExposure.maskedImage)
1086 if nImage
is not None:
1087 nImage.array[:, :] = stacker.n_image
1090 """Unset the mask of an image for mask planes specified in the config.
1095 The masked image to be modified.
1097 mask = maskedImage.getMask()
1098 for maskPlane
in self.config.removeMaskPlanes:
1100 mask &= ~mask.getPlaneBitMask(maskPlane)
1101 except pexExceptions.InvalidParameterError:
1102 self.log.debug(
"Unable to remove mask plane %s: no mask plane with that name was found.",
1106 def setRejectedMaskMapping(statsCtrl):
1107 """Map certain mask planes of the warps to new planes for the coadd.
1109 If a pixel is rejected due to a mask value other than EDGE, NO_DATA,
1110 or CLIPPED, set it to REJECTED on the coadd.
1111 If a pixel
is rejected due to EDGE, set the coadd pixel to SENSOR_EDGE.
1112 If a pixel
is rejected due to CLIPPED, set the coadd pixel to CLIPPED.
1117 Statistics control object
for coadd
1121 maskMap : `list` of `tuple` of `int`
1122 A list of mappings of mask planes of the warped exposures to
1123 mask planes of the coadd.
1125 edge = afwImage.Mask.getPlaneBitMask("EDGE")
1126 noData = afwImage.Mask.getPlaneBitMask(
"NO_DATA")
1127 clipped = afwImage.Mask.getPlaneBitMask(
"CLIPPED")
1128 toReject = statsCtrl.getAndMask() & (~noData) & (~edge) & (~clipped)
1129 maskMap = [(toReject, afwImage.Mask.getPlaneBitMask(
"REJECTED")),
1130 (edge, afwImage.Mask.getPlaneBitMask(
"SENSOR_EDGE")),
1135 """Apply in place alt mask formatted as SpanSets to a mask.
1141 altMaskSpans : `dict`
1142 SpanSet lists to apply. Each element contains the new mask
1143 plane name (e.g. "CLIPPED and/or "NO_DATA
") as the key,
1144 and list of SpanSets to apply to the mask.
1151 if self.config.doUsePsfMatchedPolygons:
1152 if (
"NO_DATA" in altMaskSpans)
and (
"NO_DATA" in self.config.badMaskPlanes):
1157 for spanSet
in altMaskSpans[
'NO_DATA']:
1158 spanSet.clippedTo(mask.getBBox()).clearMask(mask, self.getBadPixelMask())
1160 for plane, spanSetList
in altMaskSpans.items():
1161 maskClipValue = mask.addMaskPlane(plane)
1162 for spanSet
in spanSetList:
1163 spanSet.clippedTo(mask.getBBox()).setMask(mask, 2**maskClipValue)
1167 """Shrink coaddInputs' ccds' ValidPolygons in place.
1169 Either modify each ccd's validPolygon in place, or if CoaddInputs
1170 does not have a validPolygon, create one
from its bbox.
1174 coaddInputs : `lsst.afw.image.coaddInputs`
1178 for ccd
in coaddInputs.ccds:
1179 polyOrig = ccd.getValidPolygon()
1180 validPolyBBox = polyOrig.getBBox()
if polyOrig
else ccd.getBBox()
1181 validPolyBBox.grow(-self.config.matchingKernelSize//2)
1183 validPolygon = polyOrig.intersectionSingle(validPolyBBox)
1185 validPolygon = afwGeom.polygon.Polygon(
geom.Box2D(validPolyBBox))
1186 ccd.setValidPolygon(validPolygon)
1189 """Retrieve the bright object masks.
1191 Returns None on failure.
1201 Bright object mask
from the Butler object,
or None if it cannot
1205 return dataRef.get(datasetType=
"brightObjectMask", immediate=
True)
1206 except Exception
as e:
1207 self.log.warning(
"Unable to read brightObjectMask for %s: %s", dataRef.dataId, e)
1211 """Set the bright object masks.
1216 Exposure under consideration.
1218 Data identifier dict for patch.
1220 Table of bright objects to mask.
1223 if brightObjectMasks
is None:
1224 self.log.warning(
"Unable to apply bright object mask: none supplied")
1226 self.log.info(
"Applying %d bright object masks to %s", len(brightObjectMasks), dataId)
1227 mask = exposure.getMaskedImage().getMask()
1228 wcs = exposure.getWcs()
1229 plateScale = wcs.getPixelScale().asArcseconds()
1231 for rec
in brightObjectMasks:
1232 center =
geom.PointI(wcs.skyToPixel(rec.getCoord()))
1233 if rec[
"type"] ==
"box":
1234 assert rec[
"angle"] == 0.0, (
"Angle != 0 for mask object %s" % rec[
"id"])
1235 width = rec[
"width"].asArcseconds()/plateScale
1236 height = rec[
"height"].asArcseconds()/plateScale
1239 bbox =
geom.Box2I(center - halfSize, center + halfSize)
1242 geom.PointI(int(center[0] + 0.5*width), int(center[1] + 0.5*height)))
1243 spans = afwGeom.SpanSet(bbox)
1244 elif rec[
"type"] ==
"circle":
1245 radius = int(rec[
"radius"].asArcseconds()/plateScale)
1246 spans = afwGeom.SpanSet.fromShape(radius, offset=center)
1248 self.log.warning(
"Unexpected region type %s at %s", rec[
"type"], center)
1250 spans.clippedTo(mask.getBBox()).setMask(mask, self.brightObjectBitmask)
1253 """Set INEXACT_PSF mask plane.
1255 If any of the input images isn't represented in the coadd (due to
1256 clipped pixels or chip gaps), the `CoaddPsf` will be inexact. Flag
1262 Coadded exposure
's mask, modified in-place.
1264 mask.addMaskPlane("INEXACT_PSF")
1265 inexactPsf = mask.getPlaneBitMask(
"INEXACT_PSF")
1266 sensorEdge = mask.getPlaneBitMask(
"SENSOR_EDGE")
1267 clipped = mask.getPlaneBitMask(
"CLIPPED")
1268 rejected = mask.getPlaneBitMask(
"REJECTED")
1269 array = mask.getArray()
1270 selected = array & (sensorEdge | clipped | rejected) > 0
1271 array[selected] |= inexactPsf
1274 def _makeArgumentParser(cls):
1275 """Create an argument parser.
1277 parser = pipeBase.ArgumentParser(name=cls._DefaultName)
1278 parser.add_id_argument("--id", cls.ConfigClass().coaddName +
"Coadd_"
1279 + cls.ConfigClass().warpType +
"Warp",
1280 help=
"data ID, e.g. --id tract=12345 patch=1,2",
1281 ContainerClass=AssembleCoaddDataIdContainer)
1282 parser.add_id_argument(
"--selectId",
"calexp", help=
"data ID, e.g. --selectId visit=6789 ccd=0..9",
1283 ContainerClass=SelectDataIdContainer)
1287 def _subBBoxIter(bbox, subregionSize):
1288 """Iterate over subregions of a bbox.
1293 Bounding box over which to iterate.
1300 Next sub-bounding box of size ``subregionSize`` or smaller; each ``subBBox``
1301 is contained within ``bbox``, so it may be smaller than ``subregionSize`` at
1302 the edges of ``bbox``, but it will never be empty.
1305 raise RuntimeError(
"bbox %s is empty" % (bbox,))
1306 if subregionSize[0] < 1
or subregionSize[1] < 1:
1307 raise RuntimeError(
"subregionSize %s must be nonzero" % (subregionSize,))
1309 for rowShift
in range(0, bbox.getHeight(), subregionSize[1]):
1310 for colShift
in range(0, bbox.getWidth(), subregionSize[0]):
1313 if subBBox.isEmpty():
1314 raise RuntimeError(
"Bug: empty bbox! bbox=%s, subregionSize=%s, "
1315 "colShift=%s, rowShift=%s" %
1316 (bbox, subregionSize, colShift, rowShift))
1320 """Return list of only inputRefs with visitId in goodVisits ordered by goodVisit
1325 List of `lsst.pipe.base.connections.DeferredDatasetRef` with dataId containing visit
1327 Dictionary
with good visitIds
as the keys. Value ignored.
1331 filteredInputs : `list`
1332 Filtered
and sorted list of `lsst.pipe.base.connections.DeferredDatasetRef`
1334 inputWarpDict = {inputRef.ref.dataId['visit']: inputRef
for inputRef
in inputs}
1336 for visit
in goodVisits.keys():
1337 if visit
in inputWarpDict:
1338 filteredInputs.append(inputWarpDict[visit])
1339 return filteredInputs
1343 """A version of `lsst.pipe.base.DataIdContainer` specialized for assembleCoadd.
1347 """Make self.refList from self.idList.
1352 Results of parsing command-line (with ``butler``
and ``log`` elements).
1354 datasetType = namespace.config.coaddName + "Coadd"
1355 keysCoadd = namespace.butler.getKeys(datasetType=datasetType, level=self.level)
1357 for dataId
in self.idList:
1359 for key
in keysCoadd:
1360 if key
not in dataId:
1361 raise RuntimeError(
"--id must include " + key)
1363 dataRef = namespace.butler.dataRef(
1364 datasetType=datasetType,
1367 self.refList.append(dataRef)
1371 """Function to count the number of pixels with a specific mask in a
1374 Find the intersection of mask & footprint. Count all pixels in the mask
1375 that are
in the intersection that have bitmask set but do
not have
1376 ignoreMask set. Return the count.
1381 Mask to define intersection region by.
1383 Footprint to define the intersection region by.
1385 Specific mask that we wish to count the number of occurances of.
1387 Pixels to
not consider.
1392 Count of number of pixels
in footprint
with specified mask.
1394 bbox = footprint.getBBox()
1395 bbox.clip(mask.getBBox(afwImage.PARENT))
1396 fp = afwImage.Mask(bbox)
1397 subMask = mask.Factory(mask, bbox, afwImage.PARENT)
1398 footprint.spans.setMask(fp, bitmask)
1399 return numpy.logical_and((subMask.getArray() & fp.getArray()) > 0,
1400 (subMask.getArray() & ignoreMask) == 0).sum()
1404 """Configuration parameters for the SafeClipAssembleCoaddTask.
1406 clipDetection = pexConfig.ConfigurableField(
1407 target=SourceDetectionTask,
1408 doc="Detect sources on difference between unclipped and clipped coadd")
1409 minClipFootOverlap = pexConfig.Field(
1410 doc=
"Minimum fractional overlap of clipped footprint with visit DETECTED to be clipped",
1414 minClipFootOverlapSingle = pexConfig.Field(
1415 doc=
"Minimum fractional overlap of clipped footprint with visit DETECTED to be "
1416 "clipped when only one visit overlaps",
1420 minClipFootOverlapDouble = pexConfig.Field(
1421 doc=
"Minimum fractional overlap of clipped footprints with visit DETECTED to be "
1422 "clipped when two visits overlap",
1426 maxClipFootOverlapDouble = pexConfig.Field(
1427 doc=
"Maximum fractional overlap of clipped footprints with visit DETECTED when "
1428 "considering two visits",
1432 minBigOverlap = pexConfig.Field(
1433 doc=
"Minimum number of pixels in footprint to use DETECTED mask from the single visits "
1434 "when labeling clipped footprints",
1440 """Set default values for clipDetection.
1444 The numeric values for these configuration parameters were
1445 empirically determined, future work may further refine them.
1447 AssembleCoaddConfig.setDefaults(self)
1463 log.warning(
"Additional Sigma-clipping not allowed in Safe-clipped Coadds. "
1464 "Ignoring doSigmaClip.")
1467 raise ValueError(
"Only MEAN statistic allowed for final stacking in SafeClipAssembleCoadd "
1468 "(%s chosen). Please set statistic to MEAN."
1470 AssembleCoaddTask.ConfigClass.validate(self)
1474 """Assemble a coadded image from a set of coadded temporary exposures,
1475 being careful to clip & flag areas with potential artifacts.
1477 In ``AssembleCoaddTask``, we compute the coadd
as an clipped mean (i.e.,
1478 we clip outliers). The problem
with doing this
is that when computing the
1479 coadd PSF at a given location, individual visit PSFs
from visits
with
1480 outlier pixels contribute to the coadd PSF
and cannot be treated correctly.
1481 In this task, we correct
for this behavior by creating a new
1482 ``badMaskPlane``
'CLIPPED'. We populate this plane on the input
1483 coaddTempExps
and the final coadd where
1485 i. difference imaging suggests that there
is an outlier
and
1486 ii. this outlier appears on only one
or two images.
1488 Such regions will
not contribute to the final coadd. Furthermore, any
1489 routine to determine the coadd PSF can now be cognizant of clipped regions.
1490 Note that the algorithm implemented by this task
is preliminary
and works
1491 correctly
for HSC data. Parameter modifications
and or considerable
1492 redesigning of the algorithm
is likley required
for other surveys.
1494 ``SafeClipAssembleCoaddTask`` uses a ``SourceDetectionTask``
1495 "clipDetection" subtask
and also sub-classes ``AssembleCoaddTask``.
1496 You can retarget the ``SourceDetectionTask``
"clipDetection" subtask
1501 The `lsst.pipe.base.cmdLineTask.CmdLineTask` interface supports a
1502 flag ``-d`` to
import ``debug.py``
from your ``PYTHONPATH``;
1503 see `baseDebug`
for more about ``debug.py`` files.
1504 `SafeClipAssembleCoaddTask` has no debug variables of its own.
1505 The ``SourceDetectionTask``
"clipDetection" subtasks may support debug
1506 variables. See the documetation
for `SourceDetectionTask`
"clipDetection"
1507 for further information.
1511 `SafeClipAssembleCoaddTask` assembles a set of warped ``coaddTempExp``
1512 images into a coadded image. The `SafeClipAssembleCoaddTask`
is invoked by
1513 running assembleCoadd.py *without* the flag
'--legacyCoadd'.
1515 Usage of ``assembleCoadd.py`` expects a data reference to the tract patch
1516 and filter to be coadded (specified using
1517 '--id = [KEY=VALUE1[^VALUE2[^VALUE3...] [KEY=VALUE1[^VALUE2[^VALUE3...] ...]]')
1518 along
with a list of coaddTempExps to attempt to coadd (specified using
1519 '--selectId [KEY=VALUE1[^VALUE2[^VALUE3...] [KEY=VALUE1[^VALUE2[^VALUE3...] ...]]').
1520 Only the coaddTempExps that cover the specified tract
and patch will be
1521 coadded. A list of the available optional arguments can be obtained by
1522 calling assembleCoadd.py
with the --help command line argument:
1524 .. code-block:: none
1526 assembleCoadd.py --help
1528 To demonstrate usage of the `SafeClipAssembleCoaddTask`
in the larger
1529 context of multi-band processing, we will generate the HSC-I & -R band
1530 coadds
from HSC engineering test data provided
in the ci_hsc package.
1531 To begin, assuming that the lsst stack has been already set up, we must
1532 set up the obs_subaru
and ci_hsc packages. This defines the environment
1533 variable $CI_HSC_DIR
and points at the location of the package. The raw
1534 HSC data live
in the ``$CI_HSC_DIR/raw`` directory. To begin assembling
1535 the coadds, we must first
1538 process the individual ccds
in $CI_HSC_RAW to produce calibrated exposures
1540 create a skymap that covers the area of the sky present
in the raw exposures
1541 - ``makeCoaddTempExp``
1542 warp the individual calibrated exposures to the tangent plane of the coadd</DD>
1544 We can perform all of these steps by running
1546 .. code-block:: none
1548 $CI_HSC_DIR scons warp-903986 warp-904014 warp-903990 warp-904010 warp-903988
1550 This will produce warped coaddTempExps
for each visit. To coadd the
1551 warped data, we call ``assembleCoadd.py``
as follows:
1553 .. code-block:: none
1555 assembleCoadd.py $CI_HSC_DIR/DATA --id patch=5,4 tract=0 filter=HSC-I \
1556 --selectId visit=903986 ccd=16 --selectId visit=903986 ccd=22 --selectId visit=903986 ccd=23 \
1557 --selectId visit=903986 ccd=100--selectId visit=904014 ccd=1 --selectId visit=904014 ccd=6 \
1558 --selectId visit=904014 ccd=12 --selectId visit=903990 ccd=18 --selectId visit=903990 ccd=25 \
1559 --selectId visit=904010 ccd=4 --selectId visit=904010 ccd=10 --selectId visit=904010 ccd=100 \
1560 --selectId visit=903988 ccd=16 --selectId visit=903988 ccd=17 --selectId visit=903988 ccd=23 \
1561 --selectId visit=903988 ccd=24
1563 This will process the HSC-I band data. The results are written
in
1564 ``$CI_HSC_DIR/DATA/deepCoadd-results/HSC-I``.
1566 You may also choose to run:
1568 .. code-block:: none
1570 scons warp-903334 warp-903336 warp-903338 warp-903342 warp-903344 warp-903346 nnn
1571 assembleCoadd.py $CI_HSC_DIR/DATA --id patch=5,4 tract=0 filter=HSC-R --selectId visit=903334 ccd=16 \
1572 --selectId visit=903334 ccd=22 --selectId visit=903334 ccd=23 --selectId visit=903334 ccd=100 \
1573 --selectId visit=903336 ccd=17 --selectId visit=903336 ccd=24 --selectId visit=903338 ccd=18 \
1574 --selectId visit=903338 ccd=25 --selectId visit=903342 ccd=4 --selectId visit=903342 ccd=10 \
1575 --selectId visit=903342 ccd=100 --selectId visit=903344 ccd=0 --selectId visit=903344 ccd=5 \
1576 --selectId visit=903344 ccd=11 --selectId visit=903346 ccd=1 --selectId visit=903346 ccd=6 \
1577 --selectId visit=903346 ccd=12
1579 to generate the coadd
for the HSC-R band
if you are interested
in following
1580 multiBand Coadd processing
as discussed
in ``pipeTasks_multiBand``.
1582 ConfigClass = SafeClipAssembleCoaddConfig
1583 _DefaultName = "safeClipAssembleCoadd"
1586 AssembleCoaddTask.__init__(self, *args, **kwargs)
1587 schema = afwTable.SourceTable.makeMinimalSchema()
1588 self.makeSubtask(
"clipDetection", schema=schema)
1590 @utils.inheritDoc(AssembleCoaddTask)
1591 @pipeBase.timeMethod
1592 def run(self, skyInfo, tempExpRefList, imageScalerList, weightList, *args, **kwargs):
1593 """Assemble the coadd for a region.
1595 Compute the difference of coadds created with and without outlier
1596 rejection to identify coadd pixels that have outlier values
in some
1598 Detect clipped regions on the difference image
and mark these regions
1599 on the one
or two individual coaddTempExps where they occur
if there
1600 is significant overlap between the clipped region
and a source. This
1601 leaves us
with a set of footprints
from the difference image that have
1602 been identified
as having occured on just one
or two individual visits.
1603 However, these footprints were generated
from a difference image. It
1604 is conceivable
for a large diffuse source to have become broken up
1605 into multiple footprints acrosss the coadd difference
in this process.
1606 Determine the clipped region
from all overlapping footprints
from the
1607 detected sources
in each visit - these are big footprints.
1608 Combine the small
and big clipped footprints
and mark them on a new
1610 Generate the coadd using `AssembleCoaddTask.run` without outlier
1611 removal. Clipped footprints will no longer make it into the coadd
1612 because they are marked
in the new bad mask plane.
1616 args
and kwargs are passed but ignored
in order to match the call
1617 signature expected by the parent task.
1620 mask = exp.getMaskedImage().getMask()
1621 mask.addMaskPlane("CLIPPED")
1623 result = self.
detectClip(exp, tempExpRefList)
1625 self.log.info(
'Found %d clipped objects', len(result.clipFootprints))
1627 maskClipValue = mask.getPlaneBitMask(
"CLIPPED")
1628 maskDetValue = mask.getPlaneBitMask(
"DETECTED") | mask.getPlaneBitMask(
"DETECTED_NEGATIVE")
1630 bigFootprints = self.
detectClipBig(result.clipSpans, result.clipFootprints, result.clipIndices,
1631 result.detectionFootprints, maskClipValue, maskDetValue,
1634 maskClip = mask.Factory(mask.getBBox(afwImage.PARENT))
1635 afwDet.setMaskFromFootprintList(maskClip, result.clipFootprints, maskClipValue)
1637 maskClipBig = maskClip.Factory(mask.getBBox(afwImage.PARENT))
1638 afwDet.setMaskFromFootprintList(maskClipBig, bigFootprints, maskClipValue)
1639 maskClip |= maskClipBig
1642 badMaskPlanes = self.config.badMaskPlanes[:]
1643 badMaskPlanes.append(
"CLIPPED")
1644 badPixelMask = afwImage.Mask.getPlaneBitMask(badMaskPlanes)
1645 return AssembleCoaddTask.run(self, skyInfo, tempExpRefList, imageScalerList, weightList,
1646 result.clipSpans, mask=badPixelMask)
1649 """Return an exposure that contains the difference between unclipped
1652 Generate a difference image between clipped
and unclipped coadds.
1653 Compute the difference image by subtracting an outlier-clipped coadd
1654 from an outlier-unclipped coadd. Return the difference image.
1658 skyInfo : `lsst.pipe.base.Struct`
1659 Patch geometry information,
from getSkyInfo
1660 tempExpRefList : `list`
1661 List of data reference to tempExp
1662 imageScalerList : `list`
1663 List of image scalers
1670 Difference image of unclipped
and clipped coadd wrapped
in an Exposure
1672 config = AssembleCoaddConfig()
1677 configIntersection = {k: getattr(self.config, k)
1678 for k, v
in self.config.toDict().items()
1679 if (k
in config.keys()
and k !=
"connections")}
1680 configIntersection[
'doInputMap'] =
False
1681 configIntersection[
'doNImage'] =
False
1682 config.update(**configIntersection)
1685 config.statistic =
'MEAN'
1686 task = AssembleCoaddTask(config=config)
1687 coaddMean = task.run(skyInfo, tempExpRefList, imageScalerList, weightList).coaddExposure
1689 config.statistic =
'MEANCLIP'
1690 task = AssembleCoaddTask(config=config)
1691 coaddClip = task.run(skyInfo, tempExpRefList, imageScalerList, weightList).coaddExposure
1693 coaddDiff = coaddMean.getMaskedImage().Factory(coaddMean.getMaskedImage())
1694 coaddDiff -= coaddClip.getMaskedImage()
1695 exp = afwImage.ExposureF(coaddDiff)
1696 exp.setPsf(coaddMean.getPsf())
1700 """Detect clipped regions on an exposure and set the mask on the
1701 individual tempExp masks.
1703 Detect footprints in the difference image after smoothing the
1704 difference image
with a Gaussian kernal. Identify footprints that
1705 overlap
with one
or two input ``coaddTempExps`` by comparing the
1706 computed overlap fraction to thresholds set
in the config. A different
1707 threshold
is applied depending on the number of overlapping visits
1708 (restricted to one
or two). If the overlap exceeds the thresholds,
1709 the footprint
is considered
"CLIPPED" and is marked
as such on the
1710 coaddTempExp. Return a struct
with the clipped footprints, the indices
1711 of the ``coaddTempExps`` that end up overlapping
with the clipped
1712 footprints,
and a list of new masks
for the ``coaddTempExps``.
1717 Exposure to run detection on.
1718 tempExpRefList : `list`
1719 List of data reference to tempExp.
1723 result : `lsst.pipe.base.Struct`
1724 Result struct
with components:
1726 - ``clipFootprints``: list of clipped footprints.
1727 - ``clipIndices``: indices
for each ``clippedFootprint``
in
1729 - ``clipSpans``: List of dictionaries containing spanSet lists
1730 to clip. Each element contains the new maskplane name
1731 (
"CLIPPED")
as the key
and list of ``SpanSets``
as the value.
1732 - ``detectionFootprints``: List of DETECTED/DETECTED_NEGATIVE plane
1733 compressed into footprints.
1735 mask = exp.getMaskedImage().getMask()
1736 maskDetValue = mask.getPlaneBitMask("DETECTED") | mask.getPlaneBitMask(
"DETECTED_NEGATIVE")
1737 fpSet = self.clipDetection.detectFootprints(exp, doSmooth=
True, clearMask=
True)
1739 fpSet.positive.merge(fpSet.negative)
1740 footprints = fpSet.positive
1741 self.log.info(
'Found %d potential clipped objects', len(footprints.getFootprints()))
1742 ignoreMask = self.getBadPixelMask()
1746 artifactSpanSets = [{
'CLIPPED': list()}
for _
in tempExpRefList]
1749 visitDetectionFootprints = []
1751 dims = [len(tempExpRefList), len(footprints.getFootprints())]
1752 overlapDetArr = numpy.zeros(dims, dtype=numpy.uint16)
1753 ignoreArr = numpy.zeros(dims, dtype=numpy.uint16)
1756 for i, warpRef
in enumerate(tempExpRefList):
1757 tmpExpMask = warpRef.get(datasetType=self.getTempExpDatasetName(self.warpType),
1758 immediate=
True).getMaskedImage().getMask()
1759 maskVisitDet = tmpExpMask.Factory(tmpExpMask, tmpExpMask.getBBox(afwImage.PARENT),
1760 afwImage.PARENT,
True)
1761 maskVisitDet &= maskDetValue
1762 visitFootprints = afwDet.FootprintSet(maskVisitDet, afwDet.Threshold(1))
1763 visitDetectionFootprints.append(visitFootprints)
1765 for j, footprint
in enumerate(footprints.getFootprints()):
1770 for j, footprint
in enumerate(footprints.getFootprints()):
1771 nPixel = footprint.getArea()
1774 for i
in range(len(tempExpRefList)):
1775 ignore = ignoreArr[i, j]
1776 overlapDet = overlapDetArr[i, j]
1777 totPixel = nPixel - ignore
1780 if ignore > overlapDet
or totPixel <= 0.5*nPixel
or overlapDet == 0:
1782 overlap.append(overlapDet/float(totPixel))
1785 overlap = numpy.array(overlap)
1786 if not len(overlap):
1793 if len(overlap) == 1:
1794 if overlap[0] > self.config.minClipFootOverlapSingle:
1799 clipIndex = numpy.where(overlap > self.config.minClipFootOverlap)[0]
1800 if len(clipIndex) == 1:
1802 keepIndex = [clipIndex[0]]
1805 clipIndex = numpy.where(overlap > self.config.minClipFootOverlapDouble)[0]
1806 if len(clipIndex) == 2
and len(overlap) > 3:
1807 clipIndexComp = numpy.where(overlap <= self.config.minClipFootOverlapDouble)[0]
1808 if numpy.max(overlap[clipIndexComp]) <= self.config.maxClipFootOverlapDouble:
1810 keepIndex = clipIndex
1815 for index
in keepIndex:
1816 globalIndex = indexList[index]
1817 artifactSpanSets[globalIndex][
'CLIPPED'].append(footprint.spans)
1819 clipIndices.append(numpy.array(indexList)[keepIndex])
1820 clipFootprints.append(footprint)
1822 return pipeBase.Struct(clipFootprints=clipFootprints, clipIndices=clipIndices,
1823 clipSpans=artifactSpanSets, detectionFootprints=visitDetectionFootprints)
1825 def detectClipBig(self, clipList, clipFootprints, clipIndices, detectionFootprints,
1826 maskClipValue, maskDetValue, coaddBBox):
1827 """Return individual warp footprints for large artifacts and append
1828 them to ``clipList`` in place.
1830 Identify big footprints composed of many sources
in the coadd
1831 difference that may have originated
in a large diffuse source
in the
1832 coadd. We do this by indentifying all clipped footprints that overlap
1833 significantly
with each source
in all the coaddTempExps.
1838 List of alt mask SpanSets
with clipping information. Modified.
1839 clipFootprints : `list`
1840 List of clipped footprints.
1841 clipIndices : `list`
1842 List of which entries
in tempExpClipList each footprint belongs to.
1844 Mask value of clipped pixels.
1846 Mask value of detected pixels.
1847 coaddBBox : `lsst.geom.Box`
1848 BBox of the coadd
and warps.
1852 bigFootprintsCoadd : `list`
1853 List of big footprints
1855 bigFootprintsCoadd = []
1856 ignoreMask = self.getBadPixelMask()
1857 for index, (clippedSpans, visitFootprints)
in enumerate(zip(clipList, detectionFootprints)):
1858 maskVisitDet = afwImage.MaskX(coaddBBox, 0x0)
1859 for footprint
in visitFootprints.getFootprints():
1860 footprint.spans.setMask(maskVisitDet, maskDetValue)
1863 clippedFootprintsVisit = []
1864 for foot, clipIndex
in zip(clipFootprints, clipIndices):
1865 if index
not in clipIndex:
1867 clippedFootprintsVisit.append(foot)
1868 maskVisitClip = maskVisitDet.Factory(maskVisitDet.getBBox(afwImage.PARENT))
1869 afwDet.setMaskFromFootprintList(maskVisitClip, clippedFootprintsVisit, maskClipValue)
1871 bigFootprintsVisit = []
1872 for foot
in visitFootprints.getFootprints():
1873 if foot.getArea() < self.config.minBigOverlap:
1876 if nCount > self.config.minBigOverlap:
1877 bigFootprintsVisit.append(foot)
1878 bigFootprintsCoadd.append(foot)
1880 for footprint
in bigFootprintsVisit:
1881 clippedSpans[
"CLIPPED"].append(footprint.spans)
1883 return bigFootprintsCoadd
1887 psfMatchedWarps = pipeBase.connectionTypes.Input(
1888 doc=(
"PSF-Matched Warps are required by CompareWarp regardless of the coadd type requested. "
1889 "Only PSF-Matched Warps make sense for image subtraction. "
1890 "Therefore, they must be an additional declared input."),
1891 name=
"{inputCoaddName}Coadd_psfMatchedWarp",
1892 storageClass=
"ExposureF",
1893 dimensions=(
"tract",
"patch",
"skymap",
"visit"),
1897 templateCoadd = pipeBase.connectionTypes.Output(
1898 doc=(
"Model of the static sky, used to find temporal artifacts. Typically a PSF-Matched, "
1899 "sigma-clipped coadd. Written if and only if assembleStaticSkyModel.doWrite=True"),
1900 name=
"{outputCoaddName}CoaddPsfMatched",
1901 storageClass=
"ExposureF",
1902 dimensions=(
"tract",
"patch",
"skymap",
"band"),
1907 if not config.assembleStaticSkyModel.doWrite:
1908 self.outputs.remove(
"templateCoadd")
1913 pipelineConnections=CompareWarpAssembleCoaddConnections):
1914 assembleStaticSkyModel = pexConfig.ConfigurableField(
1915 target=AssembleCoaddTask,
1916 doc=
"Task to assemble an artifact-free, PSF-matched Coadd to serve as a"
1917 " naive/first-iteration model of the static sky.",
1919 detect = pexConfig.ConfigurableField(
1920 target=SourceDetectionTask,
1921 doc=
"Detect outlier sources on difference between each psfMatched warp and static sky model"
1923 detectTemplate = pexConfig.ConfigurableField(
1924 target=SourceDetectionTask,
1925 doc=
"Detect sources on static sky model. Only used if doPreserveContainedBySource is True"
1927 maskStreaks = pexConfig.ConfigurableField(
1928 target=MaskStreaksTask,
1929 doc=
"Detect streaks on difference between each psfMatched warp and static sky model. Only used if "
1930 "doFilterMorphological is True. Adds a mask plane to an exposure, with the mask plane name set by"
1933 streakMaskName = pexConfig.Field(
1936 doc=
"Name of mask bit used for streaks"
1938 maxNumEpochs = pexConfig.Field(
1939 doc=
"Charactistic maximum local number of epochs/visits in which an artifact candidate can appear "
1940 "and still be masked. The effective maxNumEpochs is a broken linear function of local "
1941 "number of epochs (N): min(maxFractionEpochsLow*N, maxNumEpochs + maxFractionEpochsHigh*N). "
1942 "For each footprint detected on the image difference between the psfMatched warp and static sky "
1943 "model, if a significant fraction of pixels (defined by spatialThreshold) are residuals in more "
1944 "than the computed effective maxNumEpochs, the artifact candidate is deemed persistant rather "
1945 "than transient and not masked.",
1949 maxFractionEpochsLow = pexConfig.RangeField(
1950 doc=
"Fraction of local number of epochs (N) to use as effective maxNumEpochs for low N. "
1951 "Effective maxNumEpochs = "
1952 "min(maxFractionEpochsLow * N, maxNumEpochs + maxFractionEpochsHigh * N)",
1957 maxFractionEpochsHigh = pexConfig.RangeField(
1958 doc=
"Fraction of local number of epochs (N) to use as effective maxNumEpochs for high N. "
1959 "Effective maxNumEpochs = "
1960 "min(maxFractionEpochsLow * N, maxNumEpochs + maxFractionEpochsHigh * N)",
1965 spatialThreshold = pexConfig.RangeField(
1966 doc=
"Unitless fraction of pixels defining how much of the outlier region has to meet the "
1967 "temporal criteria. If 0, clip all. If 1, clip none.",
1971 inclusiveMin=
True, inclusiveMax=
True
1973 doScaleWarpVariance = pexConfig.Field(
1974 doc=
"Rescale Warp variance plane using empirical noise?",
1978 scaleWarpVariance = pexConfig.ConfigurableField(
1979 target=ScaleVarianceTask,
1980 doc=
"Rescale variance on warps",
1982 doPreserveContainedBySource = pexConfig.Field(
1983 doc=
"Rescue artifacts from clipping that completely lie within a footprint detected"
1984 "on the PsfMatched Template Coadd. Replicates a behavior of SafeClip.",
1988 doPrefilterArtifacts = pexConfig.Field(
1989 doc=
"Ignore artifact candidates that are mostly covered by the bad pixel mask, "
1990 "because they will be excluded anyway. This prevents them from contributing "
1991 "to the outlier epoch count image and potentially being labeled as persistant."
1992 "'Mostly' is defined by the config 'prefilterArtifactsRatio'.",
1996 prefilterArtifactsMaskPlanes = pexConfig.ListField(
1997 doc=
"Prefilter artifact candidates that are mostly covered by these bad mask planes.",
1999 default=(
'NO_DATA',
'BAD',
'SAT',
'SUSPECT'),
2001 prefilterArtifactsRatio = pexConfig.Field(
2002 doc=
"Prefilter artifact candidates with less than this fraction overlapping good pixels",
2006 doFilterMorphological = pexConfig.Field(
2007 doc=
"Filter artifact candidates based on morphological criteria, i.g. those that appear to "
2014 AssembleCoaddConfig.setDefaults(self)
2020 if "EDGE" in self.badMaskPlanes:
2021 self.badMaskPlanes.remove(
'EDGE')
2022 self.removeMaskPlanes.append(
'EDGE')
2031 self.
detect.doTempLocalBackground =
False
2032 self.
detect.reEstimateBackground =
False
2033 self.
detect.returnOriginalFootprints =
False
2034 self.
detect.thresholdPolarity =
"both"
2035 self.
detect.thresholdValue = 5
2036 self.
detect.minPixels = 4
2037 self.
detect.isotropicGrow =
True
2038 self.
detect.thresholdType =
"pixel_stdev"
2039 self.
detect.nSigmaToGrow = 0.4
2050 raise ValueError(
"No dataset type exists for a PSF-Matched Template N Image."
2051 "Please set assembleStaticSkyModel.doNImage=False")
2054 raise ValueError(
"warpType (%s) == assembleStaticSkyModel.warpType (%s) and will compete for "
2055 "the same dataset name. Please set assembleStaticSkyModel.doWrite to False "
2056 "or warpType to 'direct'. assembleStaticSkyModel.warpType should ways be "
2061 """Assemble a compareWarp coadded image from a set of warps
2062 by masking artifacts detected by comparing PSF-matched warps.
2064 In ``AssembleCoaddTask``, we compute the coadd as an clipped mean (i.e.,
2065 we clip outliers). The problem
with doing this
is that when computing the
2066 coadd PSF at a given location, individual visit PSFs
from visits
with
2067 outlier pixels contribute to the coadd PSF
and cannot be treated correctly.
2068 In this task, we correct
for this behavior by creating a new badMaskPlane
2069 'CLIPPED' which marks pixels
in the individual warps suspected to contain
2070 an artifact. We populate this plane on the input warps by comparing
2071 PSF-matched warps
with a PSF-matched median coadd which serves
as a
2072 model of the static sky. Any group of pixels that deviates
from the
2073 PSF-matched template coadd by more than config.detect.threshold sigma,
2074 is an artifact candidate. The candidates are then filtered to remove
2075 variable sources
and sources that are difficult to subtract such
as
2076 bright stars. This filter
is configured using the config parameters
2077 ``temporalThreshold``
and ``spatialThreshold``. The temporalThreshold
is
2078 the maximum fraction of epochs that the deviation can appear
in and still
2079 be considered an artifact. The spatialThreshold
is the maximum fraction of
2080 pixels
in the footprint of the deviation that appear
in other epochs
2081 (where other epochs
is defined by the temporalThreshold). If the deviant
2082 region meets this criteria of having a significant percentage of pixels
2083 that deviate
in only a few epochs, these pixels have the
'CLIPPED' bit
2084 set
in the mask. These regions will
not contribute to the final coadd.
2085 Furthermore, any routine to determine the coadd PSF can now be cognizant
2086 of clipped regions. Note that the algorithm implemented by this task
is
2087 preliminary
and works correctly
for HSC data. Parameter modifications
and
2088 or considerable redesigning of the algorithm
is likley required
for other
2091 ``CompareWarpAssembleCoaddTask`` sub-classes
2092 ``AssembleCoaddTask``
and instantiates ``AssembleCoaddTask``
2093 as a subtask to generate the TemplateCoadd (the model of the static sky).
2097 The `lsst.pipe.base.cmdLineTask.CmdLineTask` interface supports a
2098 flag ``-d`` to
import ``debug.py``
from your ``PYTHONPATH``; see
2099 ``baseDebug``
for more about ``debug.py`` files.
2101 This task supports the following debug variables:
2104 If
True then save the Epoch Count Image
as a fits file
in the `figPath`
2106 Path to save the debug fits images
and figures
2108 For example, put something like:
2110 .. code-block:: python
2113 def DebugInfo(name):
2115 if name ==
"lsst.pipe.tasks.assembleCoadd":
2116 di.saveCountIm =
True
2117 di.figPath =
"/desired/path/to/debugging/output/images"
2121 into your ``debug.py`` file
and run ``assemebleCoadd.py``
with the
2122 ``--debug`` flag. Some subtasks may have their own debug variables;
2123 see individual Task documentation.
2127 ``CompareWarpAssembleCoaddTask`` assembles a set of warped images into a
2128 coadded image. The ``CompareWarpAssembleCoaddTask``
is invoked by running
2129 ``assembleCoadd.py``
with the flag ``--compareWarpCoadd``.
2130 Usage of ``assembleCoadd.py`` expects a data reference to the tract patch
2131 and filter to be coadded (specified using
2132 '--id = [KEY=VALUE1[^VALUE2[^VALUE3...] [KEY=VALUE1[^VALUE2[^VALUE3...] ...]]')
2133 along
with a list of coaddTempExps to attempt to coadd (specified using
2134 '--selectId [KEY=VALUE1[^VALUE2[^VALUE3...] [KEY=VALUE1[^VALUE2[^VALUE3...] ...]]').
2135 Only the warps that cover the specified tract
and patch will be coadded.
2136 A list of the available optional arguments can be obtained by calling
2137 ``assembleCoadd.py``
with the ``--help`` command line argument:
2139 .. code-block:: none
2141 assembleCoadd.py --help
2143 To demonstrate usage of the ``CompareWarpAssembleCoaddTask``
in the larger
2144 context of multi-band processing, we will generate the HSC-I & -R band
2145 oadds
from HSC engineering test data provided
in the ``ci_hsc`` package.
2146 To begin, assuming that the lsst stack has been already set up, we must
2147 set up the ``obs_subaru``
and ``ci_hsc`` packages.
2148 This defines the environment variable ``$CI_HSC_DIR``
and points at the
2149 location of the package. The raw HSC data live
in the ``$CI_HSC_DIR/raw``
2150 directory. To begin assembling the coadds, we must first
2153 process the individual ccds
in $CI_HSC_RAW to produce calibrated exposures
2155 create a skymap that covers the area of the sky present
in the raw exposures
2157 warp the individual calibrated exposures to the tangent plane of the coadd
2159 We can perform all of these steps by running
2161 .. code-block:: none
2163 $CI_HSC_DIR scons warp-903986 warp-904014 warp-903990 warp-904010 warp-903988
2165 This will produce warped ``coaddTempExps``
for each visit. To coadd the
2166 warped data, we call ``assembleCoadd.py``
as follows:
2168 .. code-block:: none
2170 assembleCoadd.py --compareWarpCoadd $CI_HSC_DIR/DATA --id patch=5,4 tract=0 filter=HSC-I \
2171 --selectId visit=903986 ccd=16 --selectId visit=903986 ccd=22 --selectId visit=903986 ccd=23 \
2172 --selectId visit=903986 ccd=100 --selectId visit=904014 ccd=1 --selectId visit=904014 ccd=6 \
2173 --selectId visit=904014 ccd=12 --selectId visit=903990 ccd=18 --selectId visit=903990 ccd=25 \
2174 --selectId visit=904010 ccd=4 --selectId visit=904010 ccd=10 --selectId visit=904010 ccd=100 \
2175 --selectId visit=903988 ccd=16 --selectId visit=903988 ccd=17 --selectId visit=903988 ccd=23 \
2176 --selectId visit=903988 ccd=24
2178 This will process the HSC-I band data. The results are written
in
2179 ``$CI_HSC_DIR/DATA/deepCoadd-results/HSC-I``.
2181 ConfigClass = CompareWarpAssembleCoaddConfig
2182 _DefaultName = "compareWarpAssembleCoadd"
2185 AssembleCoaddTask.__init__(self, *args, **kwargs)
2186 self.makeSubtask(
"assembleStaticSkyModel")
2187 detectionSchema = afwTable.SourceTable.makeMinimalSchema()
2188 self.makeSubtask(
"detect", schema=detectionSchema)
2189 if self.config.doPreserveContainedBySource:
2190 self.makeSubtask(
"detectTemplate", schema=afwTable.SourceTable.makeMinimalSchema())
2191 if self.config.doScaleWarpVariance:
2192 self.makeSubtask(
"scaleWarpVariance")
2193 if self.config.doFilterMorphological:
2194 self.makeSubtask(
"maskStreaks")
2196 @utils.inheritDoc(AssembleCoaddTask)
2199 Generate a templateCoadd to use as a naive model of static sky to
2200 subtract
from PSF-Matched warps.
2204 result : `lsst.pipe.base.Struct`
2205 Result struct
with components:
2211 staticSkyModelInputRefs = copy.deepcopy(inputRefs)
2212 staticSkyModelInputRefs.inputWarps = inputRefs.psfMatchedWarps
2216 staticSkyModelOutputRefs = copy.deepcopy(outputRefs)
2217 if self.config.assembleStaticSkyModel.doWrite:
2218 staticSkyModelOutputRefs.coaddExposure = staticSkyModelOutputRefs.templateCoadd
2221 del outputRefs.templateCoadd
2222 del staticSkyModelOutputRefs.templateCoadd
2225 if 'nImage' in staticSkyModelOutputRefs.keys():
2226 del staticSkyModelOutputRefs.nImage
2228 templateCoadd = self.assembleStaticSkyModel.runQuantum(butlerQC, staticSkyModelInputRefs,
2229 staticSkyModelOutputRefs)
2230 if templateCoadd
is None:
2233 return pipeBase.Struct(templateCoadd=templateCoadd.coaddExposure,
2234 nImage=templateCoadd.nImage,
2235 warpRefList=templateCoadd.warpRefList,
2236 imageScalerList=templateCoadd.imageScalerList,
2237 weightList=templateCoadd.weightList)
2239 @utils.inheritDoc(AssembleCoaddTask)
2242 Generate a templateCoadd to use as a naive model of static sky to
2243 subtract
from PSF-Matched warps.
2247 result : `lsst.pipe.base.Struct`
2248 Result struct
with components:
2253 templateCoadd = self.assembleStaticSkyModel.runDataRef(dataRef, selectDataList, warpRefList)
2254 if templateCoadd
is None:
2257 return pipeBase.Struct(templateCoadd=templateCoadd.coaddExposure,
2258 nImage=templateCoadd.nImage,
2259 warpRefList=templateCoadd.warpRefList,
2260 imageScalerList=templateCoadd.imageScalerList,
2261 weightList=templateCoadd.weightList)
2263 def _noTemplateMessage(self, warpType):
2264 warpName = (warpType[0].upper() + warpType[1:])
2265 message =
"""No %(warpName)s warps were found to build the template coadd which is
2266 required to run CompareWarpAssembleCoaddTask. To continue assembling this type of coadd,
2267 first either rerun makeCoaddTempExp
with config.make%(warpName)s=
True or
2268 coaddDriver
with config.makeCoadTempExp.make%(warpName)s=
True, before assembleCoadd.
2270 Alternatively, to use another algorithm
with existing warps, retarget the CoaddDriverConfig to
2271 another algorithm like:
2274 config.assemble.retarget(SafeClipAssembleCoaddTask)
2275 """ % {"warpName": warpName}
2278 @utils.inheritDoc(AssembleCoaddTask)
2279 @pipeBase.timeMethod
2280 def run(self, skyInfo, tempExpRefList, imageScalerList, weightList,
2281 supplementaryData, *args, **kwargs):
2282 """Assemble the coadd.
2284 Find artifacts and apply them to the warps
' masks creating a list of
2285 alternative masks with a new
"CLIPPED" plane
and updated
"NO_DATA"
2286 plane. Then
pass these alternative masks to the base
class's `run`
2289 The input parameters ``supplementaryData`` is a `lsst.pipe.base.Struct`
2290 that must contain a ``templateCoadd`` that serves
as the
2291 model of the static sky.
2297 dataIds = [ref.dataId
for ref
in tempExpRefList]
2298 psfMatchedDataIds = [ref.dataId
for ref
in supplementaryData.warpRefList]
2300 if dataIds != psfMatchedDataIds:
2301 self.log.info(
"Reordering and or/padding PSF-matched visit input list")
2302 supplementaryData.warpRefList = reorderAndPadList(supplementaryData.warpRefList,
2303 psfMatchedDataIds, dataIds)
2304 supplementaryData.imageScalerList = reorderAndPadList(supplementaryData.imageScalerList,
2305 psfMatchedDataIds, dataIds)
2308 spanSetMaskList = self.
findArtifacts(supplementaryData.templateCoadd,
2309 supplementaryData.warpRefList,
2310 supplementaryData.imageScalerList)
2312 badMaskPlanes = self.config.badMaskPlanes[:]
2313 badMaskPlanes.append(
"CLIPPED")
2314 badPixelMask = afwImage.Mask.getPlaneBitMask(badMaskPlanes)
2316 result = AssembleCoaddTask.run(self, skyInfo, tempExpRefList, imageScalerList, weightList,
2317 spanSetMaskList, mask=badPixelMask)
2321 self.
applyAltEdgeMask(result.coaddExposure.maskedImage.mask, spanSetMaskList)
2325 """Propagate alt EDGE mask to SENSOR_EDGE AND INEXACT_PSF planes.
2331 altMaskList : `list`
2332 List of Dicts containing ``spanSet`` lists.
2333 Each element contains the new mask plane name (e.g. "CLIPPED
2334 and/
or "NO_DATA")
as the key,
and list of ``SpanSets`` to apply to
2337 maskValue = mask.getPlaneBitMask(["SENSOR_EDGE",
"INEXACT_PSF"])
2338 for visitMask
in altMaskList:
2339 if "EDGE" in visitMask:
2340 for spanSet
in visitMask[
'EDGE']:
2341 spanSet.clippedTo(mask.getBBox()).setMask(mask, maskValue)
2346 Loop through warps twice. The first loop builds a map with the count
2347 of how many epochs each pixel deviates
from the templateCoadd by more
2348 than ``config.chiThreshold`` sigma. The second loop takes each
2349 difference image
and filters the artifacts detected
in each using
2350 count map to filter out variable sources
and sources that are
2351 difficult to subtract cleanly.
2356 Exposure to serve
as model of static sky.
2357 tempExpRefList : `list`
2358 List of data references to warps.
2359 imageScalerList : `list`
2360 List of image scalers.
2365 List of dicts containing information about CLIPPED
2366 (i.e., artifacts), NO_DATA,
and EDGE pixels.
2369 self.log.debug("Generating Count Image, and mask lists.")
2370 coaddBBox = templateCoadd.getBBox()
2371 slateIm = afwImage.ImageU(coaddBBox)
2372 epochCountImage = afwImage.ImageU(coaddBBox)
2373 nImage = afwImage.ImageU(coaddBBox)
2374 spanSetArtifactList = []
2375 spanSetNoDataMaskList = []
2376 spanSetEdgeList = []
2377 spanSetBadMorphoList = []
2378 badPixelMask = self.getBadPixelMask()
2381 templateCoadd.mask.clearAllMaskPlanes()
2383 if self.config.doPreserveContainedBySource:
2384 templateFootprints = self.detectTemplate.detectFootprints(templateCoadd)
2386 templateFootprints =
None
2388 for warpRef, imageScaler
in zip(tempExpRefList, imageScalerList):
2390 if warpDiffExp
is not None:
2392 nImage.array += (numpy.isfinite(warpDiffExp.image.array)
2393 * ((warpDiffExp.mask.array & badPixelMask) == 0)).astype(numpy.uint16)
2394 fpSet = self.detect.detectFootprints(warpDiffExp, doSmooth=
False, clearMask=
True)
2395 fpSet.positive.merge(fpSet.negative)
2396 footprints = fpSet.positive
2398 spanSetList = [footprint.spans
for footprint
in footprints.getFootprints()]
2401 if self.config.doPrefilterArtifacts:
2405 self.detect.clearMask(warpDiffExp.mask)
2406 for spans
in spanSetList:
2407 spans.setImage(slateIm, 1, doClip=
True)
2408 spans.setMask(warpDiffExp.mask, warpDiffExp.mask.getPlaneBitMask(
"DETECTED"))
2409 epochCountImage += slateIm
2411 if self.config.doFilterMorphological:
2412 maskName = self.config.streakMaskName
2413 _ = self.maskStreaks.
run(warpDiffExp)
2414 streakMask = warpDiffExp.mask
2415 spanSetStreak = afwGeom.SpanSet.fromMask(streakMask,
2416 streakMask.getPlaneBitMask(maskName)).split()
2422 nans = numpy.where(numpy.isnan(warpDiffExp.maskedImage.image.array), 1, 0)
2423 nansMask = afwImage.makeMaskFromArray(nans.astype(afwImage.MaskPixel))
2424 nansMask.setXY0(warpDiffExp.getXY0())
2425 edgeMask = warpDiffExp.mask
2426 spanSetEdgeMask = afwGeom.SpanSet.fromMask(edgeMask,
2427 edgeMask.getPlaneBitMask(
"EDGE")).split()
2431 nansMask = afwImage.MaskX(coaddBBox, 1)
2433 spanSetEdgeMask = []
2436 spanSetNoDataMask = afwGeom.SpanSet.fromMask(nansMask).split()
2438 spanSetNoDataMaskList.append(spanSetNoDataMask)
2439 spanSetArtifactList.append(spanSetList)
2440 spanSetEdgeList.append(spanSetEdgeMask)
2441 if self.config.doFilterMorphological:
2442 spanSetBadMorphoList.append(spanSetStreak)
2446 epochCountImage.writeFits(path)
2448 for i, spanSetList
in enumerate(spanSetArtifactList):
2450 filteredSpanSetList = self.
filterArtifacts(spanSetList, epochCountImage, nImage,
2452 spanSetArtifactList[i] = filteredSpanSetList
2453 if self.config.doFilterMorphological:
2454 spanSetArtifactList[i] += spanSetBadMorphoList[i]
2457 for artifacts, noData, edge
in zip(spanSetArtifactList, spanSetNoDataMaskList, spanSetEdgeList):
2458 altMasks.append({
'CLIPPED': artifacts,
2464 """Remove artifact candidates covered by bad mask plane.
2466 Any future editing of the candidate list that does not depend on
2467 temporal information should go
in this method.
2471 spanSetList : `list`
2472 List of SpanSets representing artifact candidates.
2474 Exposure containing mask planes used to prefilter.
2478 returnSpanSetList : `list`
2479 List of SpanSets
with artifacts.
2481 badPixelMask = exp.mask.getPlaneBitMask(self.config.prefilterArtifactsMaskPlanes)
2482 goodArr = (exp.mask.array & badPixelMask) == 0
2483 returnSpanSetList = []
2484 bbox = exp.getBBox()
2485 x0, y0 = exp.getXY0()
2486 for i, span
in enumerate(spanSetList):
2487 y, x = span.clippedTo(bbox).indices()
2488 yIndexLocal = numpy.array(y) - y0
2489 xIndexLocal = numpy.array(x) - x0
2490 goodRatio = numpy.count_nonzero(goodArr[yIndexLocal, xIndexLocal])/span.getArea()
2491 if goodRatio > self.config.prefilterArtifactsRatio:
2492 returnSpanSetList.append(span)
2493 return returnSpanSetList
2495 def filterArtifacts(self, spanSetList, epochCountImage, nImage, footprintsToExclude=None):
2496 """Filter artifact candidates.
2500 spanSetList : `list`
2501 List of SpanSets representing artifact candidates.
2503 Image of accumulated number of warpDiff detections.
2505 Image of the accumulated number of total epochs contributing.
2509 maskSpanSetList : `list`
2510 List of SpanSets with artifacts.
2513 maskSpanSetList = []
2514 x0, y0 = epochCountImage.getXY0()
2515 for i, span
in enumerate(spanSetList):
2516 y, x = span.indices()
2517 yIdxLocal = [y1 - y0
for y1
in y]
2518 xIdxLocal = [x1 - x0
for x1
in x]
2519 outlierN = epochCountImage.array[yIdxLocal, xIdxLocal]
2520 totalN = nImage.array[yIdxLocal, xIdxLocal]
2523 effMaxNumEpochsHighN = (self.config.maxNumEpochs
2524 + self.config.maxFractionEpochsHigh*numpy.mean(totalN))
2525 effMaxNumEpochsLowN = self.config.maxFractionEpochsLow * numpy.mean(totalN)
2526 effectiveMaxNumEpochs = int(min(effMaxNumEpochsLowN, effMaxNumEpochsHighN))
2527 nPixelsBelowThreshold = numpy.count_nonzero((outlierN > 0)
2528 & (outlierN <= effectiveMaxNumEpochs))
2529 percentBelowThreshold = nPixelsBelowThreshold / len(outlierN)
2530 if percentBelowThreshold > self.config.spatialThreshold:
2531 maskSpanSetList.append(span)
2533 if self.config.doPreserveContainedBySource
and footprintsToExclude
is not None:
2535 filteredMaskSpanSetList = []
2536 for span
in maskSpanSetList:
2538 for footprint
in footprintsToExclude.positive.getFootprints():
2539 if footprint.spans.contains(span):
2543 filteredMaskSpanSetList.append(span)
2544 maskSpanSetList = filteredMaskSpanSetList
2546 return maskSpanSetList
2548 def _readAndComputeWarpDiff(self, warpRef, imageScaler, templateCoadd):
2549 """Fetch a warp from the butler and return a warpDiff.
2554 Butler dataRef for the warp.
2556 An image scaler object.
2558 Exposure to be substracted
from the scaled warp.
2563 Exposure of the image difference between the warp
and template.
2571 warpName = self.getTempExpDatasetName(
'psfMatched')
2572 if not isinstance(warpRef, DeferredDatasetHandle):
2573 if not warpRef.datasetExists(warpName):
2574 self.log.warning(
"Could not find %s %s; skipping it", warpName, warpRef.dataId)
2576 warp = warpRef.get(datasetType=warpName, immediate=
True)
2578 imageScaler.scaleMaskedImage(warp.getMaskedImage())
2579 mi = warp.getMaskedImage()
2580 if self.config.doScaleWarpVariance:
2582 self.scaleWarpVariance.
run(mi)
2583 except Exception
as exc:
2584 self.log.warning(
"Unable to rescale variance of warp (%s); leaving it as-is", exc)
2585 mi -= templateCoadd.getMaskedImage()
2588 def _dataRef2DebugPath(self, prefix, warpRef, coaddLevel=False):
2589 """Return a path to which to write debugging output.
2591 Creates a hyphen-delimited string of dataId values for simple filenames.
2596 Prefix
for filename.
2598 Butler dataRef to make the path
from.
2599 coaddLevel : `bool`, optional.
2600 If
True, include only coadd-level keys (e.g.,
'tract',
'patch',
2601 'filter', but no
'visit').
2606 Path
for debugging output.
2609 keys = warpRef.getButler().getKeys(self.getCoaddDatasetName(self.warpType))
2611 keys = warpRef.dataId.keys()
2612 keyList = sorted(keys, reverse=
True)
2614 filename =
"%s-%s.fits" % (prefix,
'-'.join([str(warpRef.dataId[k])
for k
in keyList]))
2615 return os.path.join(directory, filename)
def makeDataRefList(self, namespace)
def __init__(self, *config=None)
def makeSupplementaryDataGen3(self, butlerQC, inputRefs, outputRefs)
def run(self, skyInfo, tempExpRefList, imageScalerList, weightList, supplementaryData, *args, **kwargs)
def prefilterArtifacts(self, spanSetList, exp)
def findArtifacts(self, templateCoadd, tempExpRefList, imageScalerList)
def _readAndComputeWarpDiff(self, warpRef, imageScaler, templateCoadd)
def _dataRef2DebugPath(self, prefix, warpRef, coaddLevel=False)
def applyAltEdgeMask(self, mask, altMaskList)
def makeSupplementaryData(self, dataRef, selectDataList=None, warpRefList=None)
def _noTemplateMessage(self, warpType)
def filterArtifacts(self, spanSetList, epochCountImage, nImage, footprintsToExclude=None)
def __init__(self, *args, **kwargs)
def buildDifferenceImage(self, skyInfo, tempExpRefList, imageScalerList, weightList)
def detectClipBig(self, clipList, clipFootprints, clipIndices, detectionFootprints, maskClipValue, maskDetValue, coaddBBox)
def __init__(self, *args, **kwargs)
def detectClip(self, exp, tempExpRefList)
def run(self, skyInfo, tempExpRefList, imageScalerList, weightList, *args, **kwargs)
Base class for coaddition.
def prepareStats(self, mask=None)
def readBrightObjectMasks(self, dataRef)
def makeSupplementaryData(self, dataRef, selectDataList=None, warpRefList=None)
def countMaskFromFootprint(mask, footprint, bitmask, ignoreMask)
def assembleOnlineMeanCoadd(self, coaddExposure, tempExpRefList, imageScalerList, weightList, altMaskList, statsCtrl, nImage=None)
def makeSupplementaryDataGen3(self, butlerQC, inputRefs, outputRefs)
def applyAltMaskPlanes(self, mask, altMaskSpans)
def shrinkValidPolygons(self, coaddInputs)
def setBrightObjectMasks(self, exposure, brightObjectMasks, dataId=None)
def getTempExpRefList(self, patchRef, calExpRefList)
def assembleMetadata(self, coaddExposure, tempExpRefList, weightList)
def removeMaskPlanes(self, maskedImage)
def run(self, skyInfo, tempExpRefList, imageScalerList, weightList, altMaskList=None, mask=None, supplementaryData=None)
def filterWarps(self, inputs, goodVisits)
def prepareInputs(self, refList)
def assembleSubregion(self, coaddExposure, bbox, tempExpRefList, imageScalerList, weightList, altMaskList, statsFlags, statsCtrl, nImage=None)
def setInexactPsf(self, mask)
def processResults(self, coaddExposure, brightObjectMasks=None, dataId=None)