41from .coaddBase
import CoaddBaseTask, SelectDataIdContainer, makeSkyInfo, makeCoaddSuffix, reorderAndPadList
42from .interpImage
import InterpImageTask
43from .scaleZeroPoint
import ScaleZeroPointTask
44from .coaddHelpers
import groupPatchExposures, getGroupDataRef
45from .maskStreaks
import MaskStreaksTask
46from .healSparseMapping
import HealSparseInputMapTask
48from lsst.daf.butler
import DeferredDatasetHandle
49from lsst.utils.timer
import timeMethod
51__all__ = [
"AssembleCoaddTask",
"AssembleCoaddConnections",
"AssembleCoaddConfig",
52 "SafeClipAssembleCoaddTask",
"SafeClipAssembleCoaddConfig",
53 "CompareWarpAssembleCoaddTask",
"CompareWarpAssembleCoaddConfig"]
55log = logging.getLogger(__name__)
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
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,
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:
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
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
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.
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.
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.
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)
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()
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)
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
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())
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)
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 coaddInputs.ccds.sort()
914 if self.warpType ==
"psfMatched":
919 modelPsfList = [tempExp.getPsf()
for tempExp
in tempExpList]
920 modelPsfWidthList = [modelPsf.computeBBox(modelPsf.getAveragePosition()).getWidth()
921 for modelPsf
in modelPsfList]
922 psf = modelPsfList[modelPsfWidthList.index(
max(modelPsfWidthList))]
924 psf = measAlg.CoaddPsf(coaddInputs.ccds, coaddExposure.getWcs(),
925 self.config.coaddPsf.makeControl())
926 coaddExposure.setPsf(psf)
927 apCorrMap = measAlg.makeCoaddApCorrMap(coaddInputs.ccds, coaddExposure.getBBox(afwImage.PARENT),
928 coaddExposure.getWcs())
929 coaddExposure.getInfo().setApCorrMap(apCorrMap)
930 if self.config.doAttachTransmissionCurve:
931 transmissionCurve = measAlg.makeCoaddTransmissionCurve(coaddExposure.getWcs(), coaddInputs.ccds)
932 coaddExposure.getInfo().setTransmissionCurve(transmissionCurve)
935 altMaskList, statsFlags, statsCtrl, nImage=None):
936 """Assemble the coadd for a sub-region.
938 For each coaddTempExp, check for (
and swap
in) an alternative mask
939 if one
is passed. Remove mask planes listed
in
940 `config.removeMaskPlanes`. Finally, stack the actual exposures using
941 `lsst.afw.math.statisticsStack`
with the statistic specified by
942 statsFlags. Typically, the statsFlag will be one of lsst.afw.math.MEAN
for
943 a mean-stack
or `lsst.afw.math.MEANCLIP`
for outlier rejection using
944 an N-sigma clipped mean where N
and iterations are specified by
945 statsCtrl. Assign the stacked subregion back to the coadd.
950 The target exposure
for the coadd.
951 bbox : `lsst.geom.Box`
953 tempExpRefList : `list`
954 List of data reference to tempExp.
955 imageScalerList : `list`
956 List of image scalers.
960 List of alternate masks to use rather than those stored
with
961 tempExp,
or None. Each element
is dict
with keys = mask plane
962 name to which to add the spans.
963 statsFlags : `lsst.afw.math.Property`
964 Property object
for statistic
for coadd.
966 Statistics control object
for coadd.
967 nImage : `lsst.afw.image.ImageU`, optional
968 Keeps track of exposure count
for each pixel.
970 self.log.debug("Computing coadd over %s", bbox)
971 tempExpName = self.getTempExpDatasetName(self.warpType)
972 coaddExposure.mask.addMaskPlane(
"REJECTED")
973 coaddExposure.mask.addMaskPlane(
"CLIPPED")
974 coaddExposure.mask.addMaskPlane(
"SENSOR_EDGE")
975 maskMap = self.setRejectedMaskMapping(statsCtrl)
976 clipped = afwImage.Mask.getPlaneBitMask(
"CLIPPED")
978 if nImage
is not None:
979 subNImage = afwImage.ImageU(bbox.getWidth(), bbox.getHeight())
980 for tempExpRef, imageScaler, altMask
in zip(tempExpRefList, imageScalerList, altMaskList):
982 if isinstance(tempExpRef, DeferredDatasetHandle):
984 exposure = tempExpRef.get(parameters={
'bbox': bbox})
987 exposure = tempExpRef.get(tempExpName +
"_sub", bbox=bbox)
989 maskedImage = exposure.getMaskedImage()
990 mask = maskedImage.getMask()
991 if altMask
is not None:
992 self.applyAltMaskPlanes(mask, altMask)
993 imageScaler.scaleMaskedImage(maskedImage)
997 if nImage
is not None:
998 subNImage.getArray()[maskedImage.getMask().getArray() & statsCtrl.getAndMask() == 0] += 1
999 if self.config.removeMaskPlanes:
1000 self.removeMaskPlanes(maskedImage)
1001 maskedImageList.append(maskedImage)
1003 if self.config.doInputMap:
1004 visit = exposure.getInfo().getCoaddInputs().visits[0].getId()
1005 self.inputMapper.mask_warp_bbox(bbox, visit, mask, statsCtrl.getAndMask())
1007 with self.timer(
"stack"):
1011 coaddExposure.maskedImage.assign(coaddSubregion, bbox)
1012 if nImage
is not None:
1013 nImage.assign(subNImage, bbox)
1016 altMaskList, statsCtrl, nImage=None):
1017 """Assemble the coadd using the "online" method.
1019 This method takes a running sum of images and weights to save memory.
1020 It only works
for MEAN statistics.
1025 The target exposure
for the coadd.
1026 tempExpRefList : `list`
1027 List of data reference to tempExp.
1028 imageScalerList : `list`
1029 List of image scalers.
1032 altMaskList : `list`
1033 List of alternate masks to use rather than those stored
with
1034 tempExp,
or None. Each element
is dict
with keys = mask plane
1035 name to which to add the spans.
1037 Statistics control object
for coadd
1038 nImage : `lsst.afw.image.ImageU`, optional
1039 Keeps track of exposure count
for each pixel.
1041 self.log.debug("Computing online coadd.")
1042 tempExpName = self.getTempExpDatasetName(self.warpType)
1043 coaddExposure.mask.addMaskPlane(
"REJECTED")
1044 coaddExposure.mask.addMaskPlane(
"CLIPPED")
1045 coaddExposure.mask.addMaskPlane(
"SENSOR_EDGE")
1046 maskMap = self.setRejectedMaskMapping(statsCtrl)
1047 thresholdDict = AccumulatorMeanStack.stats_ctrl_to_threshold_dict(statsCtrl)
1049 bbox = coaddExposure.maskedImage.getBBox()
1052 coaddExposure.image.array.shape,
1053 statsCtrl.getAndMask(),
1054 mask_threshold_dict=thresholdDict,
1056 no_good_pixels_mask=statsCtrl.getNoGoodPixelsMask(),
1057 calc_error_from_input_variance=self.config.calcErrorFromInputVariance,
1058 compute_n_image=(nImage
is not None)
1061 for tempExpRef, imageScaler, altMask, weight
in zip(tempExpRefList,
1065 if isinstance(tempExpRef, DeferredDatasetHandle):
1067 exposure = tempExpRef.get()
1070 exposure = tempExpRef.get(tempExpName)
1072 maskedImage = exposure.getMaskedImage()
1073 mask = maskedImage.getMask()
1074 if altMask
is not None:
1075 self.applyAltMaskPlanes(mask, altMask)
1076 imageScaler.scaleMaskedImage(maskedImage)
1077 if self.config.removeMaskPlanes:
1078 self.removeMaskPlanes(maskedImage)
1080 stacker.add_masked_image(maskedImage, weight=weight)
1082 if self.config.doInputMap:
1083 visit = exposure.getInfo().getCoaddInputs().visits[0].getId()
1084 self.inputMapper.mask_warp_bbox(bbox, visit, mask, statsCtrl.getAndMask())
1086 stacker.fill_stacked_masked_image(coaddExposure.maskedImage)
1088 if nImage
is not None:
1089 nImage.array[:, :] = stacker.n_image
1092 """Unset the mask of an image for mask planes specified in the config.
1097 The masked image to be modified.
1099 mask = maskedImage.getMask()
1100 for maskPlane
in self.config.removeMaskPlanes:
1102 mask &= ~mask.getPlaneBitMask(maskPlane)
1104 self.log.
debug(
"Unable to remove mask plane %s: no mask plane with that name was found.",
1108 def setRejectedMaskMapping(statsCtrl):
1109 """Map certain mask planes of the warps to new planes for the coadd.
1111 If a pixel is rejected due to a mask value other than EDGE, NO_DATA,
1112 or CLIPPED, set it to REJECTED on the coadd.
1113 If a pixel
is rejected due to EDGE, set the coadd pixel to SENSOR_EDGE.
1114 If a pixel
is rejected due to CLIPPED, set the coadd pixel to CLIPPED.
1119 Statistics control object
for coadd
1123 maskMap : `list` of `tuple` of `int`
1124 A list of mappings of mask planes of the warped exposures to
1125 mask planes of the coadd.
1127 edge = afwImage.Mask.getPlaneBitMask("EDGE")
1128 noData = afwImage.Mask.getPlaneBitMask(
"NO_DATA")
1129 clipped = afwImage.Mask.getPlaneBitMask(
"CLIPPED")
1130 toReject = statsCtrl.getAndMask() & (~noData) & (~edge) & (~clipped)
1131 maskMap = [(toReject, afwImage.Mask.getPlaneBitMask(
"REJECTED")),
1132 (edge, afwImage.Mask.getPlaneBitMask(
"SENSOR_EDGE")),
1137 """Apply in place alt mask formatted as SpanSets to a mask.
1143 altMaskSpans : `dict`
1144 SpanSet lists to apply. Each element contains the new mask
1145 plane name (e.g. "CLIPPED and/or "NO_DATA
") as the key,
1146 and list of SpanSets to apply to the mask.
1153 if self.config.doUsePsfMatchedPolygons:
1154 if (
"NO_DATA" in altMaskSpans)
and (
"NO_DATA" in self.config.badMaskPlanes):
1159 for spanSet
in altMaskSpans[
'NO_DATA']:
1160 spanSet.clippedTo(mask.getBBox()).clearMask(mask, self.getBadPixelMask())
1162 for plane, spanSetList
in altMaskSpans.items():
1163 maskClipValue = mask.addMaskPlane(plane)
1164 for spanSet
in spanSetList:
1165 spanSet.clippedTo(mask.getBBox()).setMask(mask, 2**maskClipValue)
1169 """Shrink coaddInputs' ccds' ValidPolygons in place.
1171 Either modify each ccd's validPolygon in place, or if CoaddInputs
1172 does not have a validPolygon, create one
from its bbox.
1176 coaddInputs : `lsst.afw.image.coaddInputs`
1180 for ccd
in coaddInputs.ccds:
1181 polyOrig = ccd.getValidPolygon()
1182 validPolyBBox = polyOrig.getBBox()
if polyOrig
else ccd.getBBox()
1183 validPolyBBox.grow(-self.config.matchingKernelSize//2)
1185 validPolygon = polyOrig.intersectionSingle(validPolyBBox)
1187 validPolygon = afwGeom.polygon.Polygon(
geom.Box2D(validPolyBBox))
1188 ccd.setValidPolygon(validPolygon)
1191 """Retrieve the bright object masks.
1193 Returns None on failure.
1203 Bright object mask
from the Butler object,
or None if it cannot
1207 return dataRef.get(datasetType=
"brightObjectMask", immediate=
True)
1208 except Exception
as e:
1209 self.log.
warning(
"Unable to read brightObjectMask for %s: %s", dataRef.dataId, e)
1213 """Set the bright object masks.
1218 Exposure under consideration.
1220 Data identifier dict for patch.
1222 Table of bright objects to mask.
1225 if brightObjectMasks
is None:
1226 self.log.
warning(
"Unable to apply bright object mask: none supplied")
1228 self.log.
info(
"Applying %d bright object masks to %s", len(brightObjectMasks), dataId)
1229 mask = exposure.getMaskedImage().getMask()
1230 wcs = exposure.getWcs()
1231 plateScale = wcs.getPixelScale().asArcseconds()
1233 for rec
in brightObjectMasks:
1234 center =
geom.PointI(wcs.skyToPixel(rec.getCoord()))
1235 if rec[
"type"] ==
"box":
1236 assert rec[
"angle"] == 0.0, (
"Angle != 0 for mask object %s" % rec[
"id"])
1237 width = rec[
"width"].asArcseconds()/plateScale
1238 height = rec[
"height"].asArcseconds()/plateScale
1241 bbox =
geom.Box2I(center - halfSize, center + halfSize)
1246 elif rec[
"type"] ==
"circle":
1247 radius =
int(rec[
"radius"].asArcseconds()/plateScale)
1248 spans = afwGeom.SpanSet.fromShape(radius, offset=center)
1250 self.log.
warning(
"Unexpected region type %s at %s", rec[
"type"], center)
1252 spans.clippedTo(mask.getBBox()).setMask(mask, self.brightObjectBitmask)
1255 """Set INEXACT_PSF mask plane.
1257 If any of the input images isn't represented in the coadd (due to
1258 clipped pixels or chip gaps), the `CoaddPsf` will be inexact. Flag
1264 Coadded exposure
's mask, modified in-place.
1266 mask.addMaskPlane("INEXACT_PSF")
1267 inexactPsf = mask.getPlaneBitMask(
"INEXACT_PSF")
1268 sensorEdge = mask.getPlaneBitMask(
"SENSOR_EDGE")
1269 clipped = mask.getPlaneBitMask(
"CLIPPED")
1270 rejected = mask.getPlaneBitMask(
"REJECTED")
1271 array = mask.getArray()
1272 selected = array & (sensorEdge | clipped | rejected) > 0
1273 array[selected] |= inexactPsf
1276 def _makeArgumentParser(cls):
1277 """Create an argument parser.
1279 parser = pipeBase.ArgumentParser(name=cls._DefaultName)
1280 parser.add_id_argument("--id", cls.ConfigClass().coaddName +
"Coadd_"
1281 + cls.ConfigClass().warpType +
"Warp",
1282 help=
"data ID, e.g. --id tract=12345 patch=1,2",
1283 ContainerClass=AssembleCoaddDataIdContainer)
1284 parser.add_id_argument(
"--selectId",
"calexp", help=
"data ID, e.g. --selectId visit=6789 ccd=0..9",
1285 ContainerClass=SelectDataIdContainer)
1289 def _subBBoxIter(bbox, subregionSize):
1290 """Iterate over subregions of a bbox.
1295 Bounding box over which to iterate.
1302 Next sub-bounding box of size ``subregionSize`` or smaller; each ``subBBox``
1303 is contained within ``bbox``, so it may be smaller than ``subregionSize`` at
1304 the edges of ``bbox``, but it will never be empty.
1307 raise RuntimeError(
"bbox %s is empty" % (bbox,))
1308 if subregionSize[0] < 1
or subregionSize[1] < 1:
1309 raise RuntimeError(
"subregionSize %s must be nonzero" % (subregionSize,))
1311 for rowShift
in range(0, bbox.getHeight(), subregionSize[1]):
1312 for colShift
in range(0, bbox.getWidth(), subregionSize[0]):
1315 if subBBox.isEmpty():
1316 raise RuntimeError(
"Bug: empty bbox! bbox=%s, subregionSize=%s, "
1317 "colShift=%s, rowShift=%s" %
1318 (bbox, subregionSize, colShift, rowShift))
1322 """Return list of only inputRefs with visitId in goodVisits ordered by goodVisit
1327 List of `lsst.pipe.base.connections.DeferredDatasetRef` with dataId containing visit
1329 Dictionary
with good visitIds
as the keys. Value ignored.
1333 filteredInputs : `list`
1334 Filtered
and sorted list of `lsst.pipe.base.connections.DeferredDatasetRef`
1336 inputWarpDict = {inputRef.ref.dataId['visit']: inputRef
for inputRef
in inputs}
1338 for visit
in goodVisits.keys():
1339 if visit
in inputWarpDict:
1340 filteredInputs.append(inputWarpDict[visit])
1341 return filteredInputs
1345 """A version of `lsst.pipe.base.DataIdContainer` specialized for assembleCoadd.
1349 """Make self.refList from self.idList.
1354 Results of parsing command-line (with ``butler``
and ``log`` elements).
1356 datasetType = namespace.config.coaddName + "Coadd"
1357 keysCoadd = namespace.butler.getKeys(datasetType=datasetType, level=self.level)
1359 for dataId
in self.idList:
1361 for key
in keysCoadd:
1362 if key
not in dataId:
1363 raise RuntimeError(
"--id must include " + key)
1365 dataRef = namespace.butler.dataRef(
1366 datasetType=datasetType,
1369 self.refList.
append(dataRef)
1373 """Function to count the number of pixels with a specific mask in a
1376 Find the intersection of mask & footprint. Count all pixels in the mask
1377 that are
in the intersection that have bitmask set but do
not have
1378 ignoreMask set. Return the count.
1383 Mask to define intersection region by.
1385 Footprint to define the intersection region by.
1387 Specific mask that we wish to count the number of occurances of.
1389 Pixels to
not consider.
1394 Count of number of pixels
in footprint
with specified mask.
1396 bbox = footprint.getBBox()
1397 bbox.clip(mask.getBBox(afwImage.PARENT))
1399 subMask = mask.Factory(mask, bbox, afwImage.PARENT)
1400 footprint.spans.setMask(fp, bitmask)
1401 return numpy.logical_and((subMask.getArray() & fp.getArray()) > 0,
1402 (subMask.getArray() & ignoreMask) == 0).sum()
1406 """Configuration parameters for the SafeClipAssembleCoaddTask.
1408 clipDetection = pexConfig.ConfigurableField(
1409 target=SourceDetectionTask,
1410 doc="Detect sources on difference between unclipped and clipped coadd")
1411 minClipFootOverlap = pexConfig.Field(
1412 doc=
"Minimum fractional overlap of clipped footprint with visit DETECTED to be clipped",
1416 minClipFootOverlapSingle = pexConfig.Field(
1417 doc=
"Minimum fractional overlap of clipped footprint with visit DETECTED to be "
1418 "clipped when only one visit overlaps",
1422 minClipFootOverlapDouble = pexConfig.Field(
1423 doc=
"Minimum fractional overlap of clipped footprints with visit DETECTED to be "
1424 "clipped when two visits overlap",
1428 maxClipFootOverlapDouble = pexConfig.Field(
1429 doc=
"Maximum fractional overlap of clipped footprints with visit DETECTED when "
1430 "considering two visits",
1434 minBigOverlap = pexConfig.Field(
1435 doc=
"Minimum number of pixels in footprint to use DETECTED mask from the single visits "
1436 "when labeling clipped footprints",
1442 """Set default values for clipDetection.
1446 The numeric values for these configuration parameters were
1447 empirically determined, future work may further refine them.
1449 AssembleCoaddConfig.setDefaults(self)
1450 self.clipDetectionclipDetection.doTempLocalBackground = False
1451 self.
clipDetectionclipDetection.reEstimateBackground =
False
1452 self.
clipDetectionclipDetection.returnOriginalFootprints =
False
1458 self.
clipDetectionclipDetection.thresholdType =
"pixel_stdev"
1465 log.warning(
"Additional Sigma-clipping not allowed in Safe-clipped Coadds. "
1466 "Ignoring doSigmaClip.")
1469 raise ValueError(
"Only MEAN statistic allowed for final stacking in SafeClipAssembleCoadd "
1470 "(%s chosen). Please set statistic to MEAN."
1472 AssembleCoaddTask.ConfigClass.validate(self)
1476 """Assemble a coadded image from a set of coadded temporary exposures,
1477 being careful to clip & flag areas with potential artifacts.
1479 In ``AssembleCoaddTask``, we compute the coadd
as an clipped mean (i.e.,
1480 we clip outliers). The problem
with doing this
is that when computing the
1481 coadd PSF at a given location, individual visit PSFs
from visits
with
1482 outlier pixels contribute to the coadd PSF
and cannot be treated correctly.
1483 In this task, we correct
for this behavior by creating a new
1484 ``badMaskPlane``
'CLIPPED'. We populate this plane on the input
1485 coaddTempExps
and the final coadd where
1487 i. difference imaging suggests that there
is an outlier
and
1488 ii. this outlier appears on only one
or two images.
1490 Such regions will
not contribute to the final coadd. Furthermore, any
1491 routine to determine the coadd PSF can now be cognizant of clipped regions.
1492 Note that the algorithm implemented by this task
is preliminary
and works
1493 correctly
for HSC data. Parameter modifications
and or considerable
1494 redesigning of the algorithm
is likley required
for other surveys.
1496 ``SafeClipAssembleCoaddTask`` uses a ``SourceDetectionTask``
1497 "clipDetection" subtask
and also sub-classes ``AssembleCoaddTask``.
1498 You can retarget the ``SourceDetectionTask``
"clipDetection" subtask
1503 The `lsst.pipe.base.cmdLineTask.CmdLineTask` interface supports a
1504 flag ``-d`` to
import ``debug.py``
from your ``PYTHONPATH``;
1505 see `baseDebug`
for more about ``debug.py`` files.
1506 `SafeClipAssembleCoaddTask` has no debug variables of its own.
1507 The ``SourceDetectionTask``
"clipDetection" subtasks may support debug
1508 variables. See the documetation
for `SourceDetectionTask`
"clipDetection"
1509 for further information.
1513 `SafeClipAssembleCoaddTask` assembles a set of warped ``coaddTempExp``
1514 images into a coadded image. The `SafeClipAssembleCoaddTask`
is invoked by
1515 running assembleCoadd.py *without* the flag
'--legacyCoadd'.
1517 Usage of ``assembleCoadd.py`` expects a data reference to the tract patch
1518 and filter to be coadded (specified using
1519 '--id = [KEY=VALUE1[^VALUE2[^VALUE3...] [KEY=VALUE1[^VALUE2[^VALUE3...] ...]]')
1520 along
with a list of coaddTempExps to attempt to coadd (specified using
1521 '--selectId [KEY=VALUE1[^VALUE2[^VALUE3...] [KEY=VALUE1[^VALUE2[^VALUE3...] ...]]').
1522 Only the coaddTempExps that cover the specified tract
and patch will be
1523 coadded. A list of the available optional arguments can be obtained by
1524 calling assembleCoadd.py
with the --help command line argument:
1526 .. code-block:: none
1528 assembleCoadd.py --help
1530 To demonstrate usage of the `SafeClipAssembleCoaddTask`
in the larger
1531 context of multi-band processing, we will generate the HSC-I & -R band
1532 coadds
from HSC engineering test data provided
in the ci_hsc package.
1533 To begin, assuming that the lsst stack has been already set up, we must
1534 set up the obs_subaru
and ci_hsc packages. This defines the environment
1535 variable $CI_HSC_DIR
and points at the location of the package. The raw
1536 HSC data live
in the ``$CI_HSC_DIR/raw`` directory. To begin assembling
1537 the coadds, we must first
1540 process the individual ccds
in $CI_HSC_RAW to produce calibrated exposures
1542 create a skymap that covers the area of the sky present
in the raw exposures
1543 - ``makeCoaddTempExp``
1544 warp the individual calibrated exposures to the tangent plane of the coadd</DD>
1546 We can perform all of these steps by running
1548 .. code-block:: none
1550 $CI_HSC_DIR scons warp-903986 warp-904014 warp-903990 warp-904010 warp-903988
1552 This will produce warped coaddTempExps
for each visit. To coadd the
1553 warped data, we call ``assembleCoadd.py``
as follows:
1555 .. code-block:: none
1557 assembleCoadd.py $CI_HSC_DIR/DATA --id patch=5,4 tract=0 filter=HSC-I \
1558 --selectId visit=903986 ccd=16 --selectId visit=903986 ccd=22 --selectId visit=903986 ccd=23 \
1559 --selectId visit=903986 ccd=100--selectId visit=904014 ccd=1 --selectId visit=904014 ccd=6 \
1560 --selectId visit=904014 ccd=12 --selectId visit=903990 ccd=18 --selectId visit=903990 ccd=25 \
1561 --selectId visit=904010 ccd=4 --selectId visit=904010 ccd=10 --selectId visit=904010 ccd=100 \
1562 --selectId visit=903988 ccd=16 --selectId visit=903988 ccd=17 --selectId visit=903988 ccd=23 \
1563 --selectId visit=903988 ccd=24
1565 This will process the HSC-I band data. The results are written
in
1566 ``$CI_HSC_DIR/DATA/deepCoadd-results/HSC-I``.
1568 You may also choose to run:
1570 .. code-block:: none
1572 scons warp-903334 warp-903336 warp-903338 warp-903342 warp-903344 warp-903346 nnn
1573 assembleCoadd.py $CI_HSC_DIR/DATA --id patch=5,4 tract=0 filter=HSC-R --selectId visit=903334 ccd=16 \
1574 --selectId visit=903334 ccd=22 --selectId visit=903334 ccd=23 --selectId visit=903334 ccd=100 \
1575 --selectId visit=903336 ccd=17 --selectId visit=903336 ccd=24 --selectId visit=903338 ccd=18 \
1576 --selectId visit=903338 ccd=25 --selectId visit=903342 ccd=4 --selectId visit=903342 ccd=10 \
1577 --selectId visit=903342 ccd=100 --selectId visit=903344 ccd=0 --selectId visit=903344 ccd=5 \
1578 --selectId visit=903344 ccd=11 --selectId visit=903346 ccd=1 --selectId visit=903346 ccd=6 \
1579 --selectId visit=903346 ccd=12
1581 to generate the coadd
for the HSC-R band
if you are interested
in following
1582 multiBand Coadd processing
as discussed
in ``pipeTasks_multiBand``.
1584 ConfigClass = SafeClipAssembleCoaddConfig
1585 _DefaultName = "safeClipAssembleCoadd"
1588 AssembleCoaddTask.__init__(self, *args, **kwargs)
1589 schema = afwTable.SourceTable.makeMinimalSchema()
1590 self.makeSubtask(
"clipDetection", schema=schema)
1592 @utils.inheritDoc(AssembleCoaddTask)
1594 def run(self, skyInfo, tempExpRefList, imageScalerList, weightList, *args, **kwargs):
1595 """Assemble the coadd for a region.
1597 Compute the difference of coadds created with and without outlier
1598 rejection to identify coadd pixels that have outlier values
in some
1600 Detect clipped regions on the difference image
and mark these regions
1601 on the one
or two individual coaddTempExps where they occur
if there
1602 is significant overlap between the clipped region
and a source. This
1603 leaves us
with a set of footprints
from the difference image that have
1604 been identified
as having occured on just one
or two individual visits.
1605 However, these footprints were generated
from a difference image. It
1606 is conceivable
for a large diffuse source to have become broken up
1607 into multiple footprints acrosss the coadd difference
in this process.
1608 Determine the clipped region
from all overlapping footprints
from the
1609 detected sources
in each visit - these are big footprints.
1610 Combine the small
and big clipped footprints
and mark them on a new
1612 Generate the coadd using `AssembleCoaddTask.run` without outlier
1613 removal. Clipped footprints will no longer make it into the coadd
1614 because they are marked
in the new bad mask plane.
1618 args
and kwargs are passed but ignored
in order to match the call
1619 signature expected by the parent task.
1621 exp = self.buildDifferenceImagebuildDifferenceImage(skyInfo, tempExpRefList, imageScalerList, weightList)
1622 mask = exp.getMaskedImage().getMask()
1623 mask.addMaskPlane("CLIPPED")
1625 result = self.
detectClipdetectClip(exp, tempExpRefList)
1627 self.log.
info(
'Found %d clipped objects', len(result.clipFootprints))
1629 maskClipValue = mask.getPlaneBitMask(
"CLIPPED")
1630 maskDetValue = mask.getPlaneBitMask(
"DETECTED") | mask.getPlaneBitMask(
"DETECTED_NEGATIVE")
1632 bigFootprints = self.
detectClipBigdetectClipBig(result.clipSpans, result.clipFootprints, result.clipIndices,
1633 result.detectionFootprints, maskClipValue, maskDetValue,
1636 maskClip = mask.Factory(mask.getBBox(afwImage.PARENT))
1637 afwDet.setMaskFromFootprintList(maskClip, result.clipFootprints, maskClipValue)
1639 maskClipBig = maskClip.Factory(mask.getBBox(afwImage.PARENT))
1640 afwDet.setMaskFromFootprintList(maskClipBig, bigFootprints, maskClipValue)
1641 maskClip |= maskClipBig
1644 badMaskPlanes = self.config.badMaskPlanes[:]
1645 badMaskPlanes.append(
"CLIPPED")
1646 badPixelMask = afwImage.Mask.getPlaneBitMask(badMaskPlanes)
1647 return AssembleCoaddTask.run(self, skyInfo, tempExpRefList, imageScalerList, weightList,
1648 result.clipSpans, mask=badPixelMask)
1651 """Return an exposure that contains the difference between unclipped
1654 Generate a difference image between clipped
and unclipped coadds.
1655 Compute the difference image by subtracting an outlier-clipped coadd
1656 from an outlier-unclipped coadd. Return the difference image.
1660 skyInfo : `lsst.pipe.base.Struct`
1661 Patch geometry information,
from getSkyInfo
1662 tempExpRefList : `list`
1663 List of data reference to tempExp
1664 imageScalerList : `list`
1665 List of image scalers
1672 Difference image of unclipped
and clipped coadd wrapped
in an Exposure
1674 config = AssembleCoaddConfig()
1679 configIntersection = {k: getattr(self.config, k)
1680 for k, v
in self.config.toDict().
items()
1681 if (k
in config.keys()
and k !=
"connections")}
1682 configIntersection[
'doInputMap'] =
False
1683 configIntersection[
'doNImage'] =
False
1684 config.update(**configIntersection)
1687 config.statistic =
'MEAN'
1688 task = AssembleCoaddTask(config=config)
1689 coaddMean = task.run(skyInfo, tempExpRefList, imageScalerList, weightList).coaddExposure
1691 config.statistic =
'MEANCLIP'
1692 task = AssembleCoaddTask(config=config)
1693 coaddClip = task.run(skyInfo, tempExpRefList, imageScalerList, weightList).coaddExposure
1695 coaddDiff = coaddMean.getMaskedImage().
Factory(coaddMean.getMaskedImage())
1696 coaddDiff -= coaddClip.getMaskedImage()
1697 exp = afwImage.ExposureF(coaddDiff)
1698 exp.setPsf(coaddMean.getPsf())
1702 """Detect clipped regions on an exposure and set the mask on the
1703 individual tempExp masks.
1705 Detect footprints in the difference image after smoothing the
1706 difference image
with a Gaussian kernal. Identify footprints that
1707 overlap
with one
or two input ``coaddTempExps`` by comparing the
1708 computed overlap fraction to thresholds set
in the config. A different
1709 threshold
is applied depending on the number of overlapping visits
1710 (restricted to one
or two). If the overlap exceeds the thresholds,
1711 the footprint
is considered
"CLIPPED" and is marked
as such on the
1712 coaddTempExp. Return a struct
with the clipped footprints, the indices
1713 of the ``coaddTempExps`` that end up overlapping
with the clipped
1714 footprints,
and a list of new masks
for the ``coaddTempExps``.
1719 Exposure to run detection on.
1720 tempExpRefList : `list`
1721 List of data reference to tempExp.
1725 result : `lsst.pipe.base.Struct`
1726 Result struct
with components:
1728 - ``clipFootprints``: list of clipped footprints.
1729 - ``clipIndices``: indices
for each ``clippedFootprint``
in
1731 - ``clipSpans``: List of dictionaries containing spanSet lists
1732 to clip. Each element contains the new maskplane name
1733 (
"CLIPPED")
as the key
and list of ``SpanSets``
as the value.
1734 - ``detectionFootprints``: List of DETECTED/DETECTED_NEGATIVE plane
1735 compressed into footprints.
1737 mask = exp.getMaskedImage().getMask()
1738 maskDetValue = mask.getPlaneBitMask("DETECTED") | mask.getPlaneBitMask(
"DETECTED_NEGATIVE")
1739 fpSet = self.clipDetection.detectFootprints(exp, doSmooth=
True, clearMask=
True)
1741 fpSet.positive.merge(fpSet.negative)
1742 footprints = fpSet.positive
1743 self.log.
info(
'Found %d potential clipped objects', len(footprints.getFootprints()))
1744 ignoreMask = self.getBadPixelMask()
1748 artifactSpanSets = [{
'CLIPPED':
list()}
for _
in tempExpRefList]
1751 visitDetectionFootprints = []
1753 dims = [len(tempExpRefList), len(footprints.getFootprints())]
1754 overlapDetArr = numpy.zeros(dims, dtype=numpy.uint16)
1755 ignoreArr = numpy.zeros(dims, dtype=numpy.uint16)
1758 for i, warpRef
in enumerate(tempExpRefList):
1759 tmpExpMask = warpRef.get(datasetType=self.getTempExpDatasetName(self.warpType),
1760 immediate=
True).getMaskedImage().getMask()
1761 maskVisitDet = tmpExpMask.Factory(tmpExpMask, tmpExpMask.getBBox(afwImage.PARENT),
1762 afwImage.PARENT,
True)
1763 maskVisitDet &= maskDetValue
1765 visitDetectionFootprints.append(visitFootprints)
1767 for j, footprint
in enumerate(footprints.getFootprints()):
1772 for j, footprint
in enumerate(footprints.getFootprints()):
1773 nPixel = footprint.getArea()
1776 for i
in range(len(tempExpRefList)):
1777 ignore = ignoreArr[i, j]
1778 overlapDet = overlapDetArr[i, j]
1779 totPixel = nPixel - ignore
1782 if ignore > overlapDet
or totPixel <= 0.5*nPixel
or overlapDet == 0:
1784 overlap.append(overlapDet/
float(totPixel))
1787 overlap = numpy.array(overlap)
1788 if not len(overlap):
1795 if len(overlap) == 1:
1796 if overlap[0] > self.config.minClipFootOverlapSingle:
1801 clipIndex = numpy.where(overlap > self.config.minClipFootOverlap)[0]
1802 if len(clipIndex) == 1:
1804 keepIndex = [clipIndex[0]]
1807 clipIndex = numpy.where(overlap > self.config.minClipFootOverlapDouble)[0]
1808 if len(clipIndex) == 2
and len(overlap) > 3:
1809 clipIndexComp = numpy.where(overlap <= self.config.minClipFootOverlapDouble)[0]
1810 if numpy.max(overlap[clipIndexComp]) <= self.config.maxClipFootOverlapDouble:
1812 keepIndex = clipIndex
1817 for index
in keepIndex:
1818 globalIndex = indexList[index]
1819 artifactSpanSets[globalIndex][
'CLIPPED'].
append(footprint.spans)
1821 clipIndices.append(numpy.array(indexList)[keepIndex])
1822 clipFootprints.append(footprint)
1824 return pipeBase.Struct(clipFootprints=clipFootprints, clipIndices=clipIndices,
1825 clipSpans=artifactSpanSets, detectionFootprints=visitDetectionFootprints)
1827 def detectClipBig(self, clipList, clipFootprints, clipIndices, detectionFootprints,
1828 maskClipValue, maskDetValue, coaddBBox):
1829 """Return individual warp footprints for large artifacts and append
1830 them to ``clipList`` in place.
1832 Identify big footprints composed of many sources
in the coadd
1833 difference that may have originated
in a large diffuse source
in the
1834 coadd. We do this by indentifying all clipped footprints that overlap
1835 significantly
with each source
in all the coaddTempExps.
1840 List of alt mask SpanSets
with clipping information. Modified.
1841 clipFootprints : `list`
1842 List of clipped footprints.
1843 clipIndices : `list`
1844 List of which entries
in tempExpClipList each footprint belongs to.
1846 Mask value of clipped pixels.
1848 Mask value of detected pixels.
1849 coaddBBox : `lsst.geom.Box`
1850 BBox of the coadd
and warps.
1854 bigFootprintsCoadd : `list`
1855 List of big footprints
1857 bigFootprintsCoadd = []
1858 ignoreMask = self.getBadPixelMask()
1859 for index, (clippedSpans, visitFootprints)
in enumerate(zip(clipList, detectionFootprints)):
1860 maskVisitDet = afwImage.MaskX(coaddBBox, 0x0)
1861 for footprint
in visitFootprints.getFootprints():
1862 footprint.spans.setMask(maskVisitDet, maskDetValue)
1865 clippedFootprintsVisit = []
1866 for foot, clipIndex
in zip(clipFootprints, clipIndices):
1867 if index
not in clipIndex:
1869 clippedFootprintsVisit.append(foot)
1870 maskVisitClip = maskVisitDet.Factory(maskVisitDet.getBBox(afwImage.PARENT))
1871 afwDet.setMaskFromFootprintList(maskVisitClip, clippedFootprintsVisit, maskClipValue)
1873 bigFootprintsVisit = []
1874 for foot
in visitFootprints.getFootprints():
1875 if foot.getArea() < self.config.minBigOverlap:
1878 if nCount > self.config.minBigOverlap:
1879 bigFootprintsVisit.append(foot)
1880 bigFootprintsCoadd.append(foot)
1882 for footprint
in bigFootprintsVisit:
1883 clippedSpans[
"CLIPPED"].
append(footprint.spans)
1885 return bigFootprintsCoadd
1889 psfMatchedWarps = pipeBase.connectionTypes.Input(
1890 doc=(
"PSF-Matched Warps are required by CompareWarp regardless of the coadd type requested. "
1891 "Only PSF-Matched Warps make sense for image subtraction. "
1892 "Therefore, they must be an additional declared input."),
1893 name=
"{inputCoaddName}Coadd_psfMatchedWarp",
1894 storageClass=
"ExposureF",
1895 dimensions=(
"tract",
"patch",
"skymap",
"visit"),
1899 templateCoadd = pipeBase.connectionTypes.Output(
1900 doc=(
"Model of the static sky, used to find temporal artifacts. Typically a PSF-Matched, "
1901 "sigma-clipped coadd. Written if and only if assembleStaticSkyModel.doWrite=True"),
1902 name=
"{outputCoaddName}CoaddPsfMatched",
1903 storageClass=
"ExposureF",
1904 dimensions=(
"tract",
"patch",
"skymap",
"band"),
1909 if not config.assembleStaticSkyModel.doWrite:
1910 self.outputs.remove(
"templateCoadd")
1915 pipelineConnections=CompareWarpAssembleCoaddConnections):
1916 assembleStaticSkyModel = pexConfig.ConfigurableField(
1917 target=AssembleCoaddTask,
1918 doc=
"Task to assemble an artifact-free, PSF-matched Coadd to serve as a"
1919 " naive/first-iteration model of the static sky.",
1921 detect = pexConfig.ConfigurableField(
1922 target=SourceDetectionTask,
1923 doc=
"Detect outlier sources on difference between each psfMatched warp and static sky model"
1925 detectTemplate = pexConfig.ConfigurableField(
1926 target=SourceDetectionTask,
1927 doc=
"Detect sources on static sky model. Only used if doPreserveContainedBySource is True"
1929 maskStreaks = pexConfig.ConfigurableField(
1930 target=MaskStreaksTask,
1931 doc=
"Detect streaks on difference between each psfMatched warp and static sky model. Only used if "
1932 "doFilterMorphological is True. Adds a mask plane to an exposure, with the mask plane name set by"
1935 streakMaskName = pexConfig.Field(
1938 doc=
"Name of mask bit used for streaks"
1940 maxNumEpochs = pexConfig.Field(
1941 doc=
"Charactistic maximum local number of epochs/visits in which an artifact candidate can appear "
1942 "and still be masked. The effective maxNumEpochs is a broken linear function of local "
1943 "number of epochs (N): min(maxFractionEpochsLow*N, maxNumEpochs + maxFractionEpochsHigh*N). "
1944 "For each footprint detected on the image difference between the psfMatched warp and static sky "
1945 "model, if a significant fraction of pixels (defined by spatialThreshold) are residuals in more "
1946 "than the computed effective maxNumEpochs, the artifact candidate is deemed persistant rather "
1947 "than transient and not masked.",
1951 maxFractionEpochsLow = pexConfig.RangeField(
1952 doc=
"Fraction of local number of epochs (N) to use as effective maxNumEpochs for low N. "
1953 "Effective maxNumEpochs = "
1954 "min(maxFractionEpochsLow * N, maxNumEpochs + maxFractionEpochsHigh * N)",
1959 maxFractionEpochsHigh = pexConfig.RangeField(
1960 doc=
"Fraction of local number of epochs (N) to use as effective maxNumEpochs for high N. "
1961 "Effective maxNumEpochs = "
1962 "min(maxFractionEpochsLow * N, maxNumEpochs + maxFractionEpochsHigh * N)",
1967 spatialThreshold = pexConfig.RangeField(
1968 doc=
"Unitless fraction of pixels defining how much of the outlier region has to meet the "
1969 "temporal criteria. If 0, clip all. If 1, clip none.",
1973 inclusiveMin=
True, inclusiveMax=
True
1975 doScaleWarpVariance = pexConfig.Field(
1976 doc=
"Rescale Warp variance plane using empirical noise?",
1980 scaleWarpVariance = pexConfig.ConfigurableField(
1981 target=ScaleVarianceTask,
1982 doc=
"Rescale variance on warps",
1984 doPreserveContainedBySource = pexConfig.Field(
1985 doc=
"Rescue artifacts from clipping that completely lie within a footprint detected"
1986 "on the PsfMatched Template Coadd. Replicates a behavior of SafeClip.",
1990 doPrefilterArtifacts = pexConfig.Field(
1991 doc=
"Ignore artifact candidates that are mostly covered by the bad pixel mask, "
1992 "because they will be excluded anyway. This prevents them from contributing "
1993 "to the outlier epoch count image and potentially being labeled as persistant."
1994 "'Mostly' is defined by the config 'prefilterArtifactsRatio'.",
1998 prefilterArtifactsMaskPlanes = pexConfig.ListField(
1999 doc=
"Prefilter artifact candidates that are mostly covered by these bad mask planes.",
2001 default=(
'NO_DATA',
'BAD',
'SAT',
'SUSPECT'),
2003 prefilterArtifactsRatio = pexConfig.Field(
2004 doc=
"Prefilter artifact candidates with less than this fraction overlapping good pixels",
2008 doFilterMorphological = pexConfig.Field(
2009 doc=
"Filter artifact candidates based on morphological criteria, i.g. those that appear to "
2014 growStreakFp = pexConfig.Field(
2015 doc=
"Grow streak footprints by this number multiplied by the PSF width",
2021 AssembleCoaddConfig.setDefaults(self)
2027 if "EDGE" in self.badMaskPlanes:
2028 self.badMaskPlanes.remove(
'EDGE')
2029 self.removeMaskPlanes.
append(
'EDGE')
2038 self.
detectdetect.doTempLocalBackground =
False
2039 self.
detectdetect.reEstimateBackground =
False
2040 self.
detectdetect.returnOriginalFootprints =
False
2041 self.
detectdetect.thresholdPolarity =
"both"
2042 self.
detectdetect.thresholdValue = 5
2043 self.
detectdetect.minPixels = 4
2044 self.
detectdetect.isotropicGrow =
True
2045 self.
detectdetect.thresholdType =
"pixel_stdev"
2046 self.
detectdetect.nSigmaToGrow = 0.4
2052 self.
detectTemplatedetectTemplate.returnOriginalFootprints =
False
2057 raise ValueError(
"No dataset type exists for a PSF-Matched Template N Image."
2058 "Please set assembleStaticSkyModel.doNImage=False")
2061 raise ValueError(
"warpType (%s) == assembleStaticSkyModel.warpType (%s) and will compete for "
2062 "the same dataset name. Please set assembleStaticSkyModel.doWrite to False "
2063 "or warpType to 'direct'. assembleStaticSkyModel.warpType should ways be "
2068 """Assemble a compareWarp coadded image from a set of warps
2069 by masking artifacts detected by comparing PSF-matched warps.
2071 In ``AssembleCoaddTask``, we compute the coadd as an clipped mean (i.e.,
2072 we clip outliers). The problem
with doing this
is that when computing the
2073 coadd PSF at a given location, individual visit PSFs
from visits
with
2074 outlier pixels contribute to the coadd PSF
and cannot be treated correctly.
2075 In this task, we correct
for this behavior by creating a new badMaskPlane
2076 'CLIPPED' which marks pixels
in the individual warps suspected to contain
2077 an artifact. We populate this plane on the input warps by comparing
2078 PSF-matched warps
with a PSF-matched median coadd which serves
as a
2079 model of the static sky. Any group of pixels that deviates
from the
2080 PSF-matched template coadd by more than config.detect.threshold sigma,
2081 is an artifact candidate. The candidates are then filtered to remove
2082 variable sources
and sources that are difficult to subtract such
as
2083 bright stars. This filter
is configured using the config parameters
2084 ``temporalThreshold``
and ``spatialThreshold``. The temporalThreshold
is
2085 the maximum fraction of epochs that the deviation can appear
in and still
2086 be considered an artifact. The spatialThreshold
is the maximum fraction of
2087 pixels
in the footprint of the deviation that appear
in other epochs
2088 (where other epochs
is defined by the temporalThreshold). If the deviant
2089 region meets this criteria of having a significant percentage of pixels
2090 that deviate
in only a few epochs, these pixels have the
'CLIPPED' bit
2091 set
in the mask. These regions will
not contribute to the final coadd.
2092 Furthermore, any routine to determine the coadd PSF can now be cognizant
2093 of clipped regions. Note that the algorithm implemented by this task
is
2094 preliminary
and works correctly
for HSC data. Parameter modifications
and
2095 or considerable redesigning of the algorithm
is likley required
for other
2098 ``CompareWarpAssembleCoaddTask`` sub-classes
2099 ``AssembleCoaddTask``
and instantiates ``AssembleCoaddTask``
2100 as a subtask to generate the TemplateCoadd (the model of the static sky).
2104 The `lsst.pipe.base.cmdLineTask.CmdLineTask` interface supports a
2105 flag ``-d`` to
import ``debug.py``
from your ``PYTHONPATH``; see
2106 ``baseDebug``
for more about ``debug.py`` files.
2108 This task supports the following debug variables:
2111 If
True then save the Epoch Count Image
as a fits file
in the `figPath`
2113 Path to save the debug fits images
and figures
2115 For example, put something like:
2117 .. code-block:: python
2120 def DebugInfo(name):
2122 if name ==
"lsst.pipe.tasks.assembleCoadd":
2123 di.saveCountIm =
True
2124 di.figPath =
"/desired/path/to/debugging/output/images"
2128 into your ``debug.py`` file
and run ``assemebleCoadd.py``
with the
2129 ``--debug`` flag. Some subtasks may have their own debug variables;
2130 see individual Task documentation.
2134 ``CompareWarpAssembleCoaddTask`` assembles a set of warped images into a
2135 coadded image. The ``CompareWarpAssembleCoaddTask``
is invoked by running
2136 ``assembleCoadd.py``
with the flag ``--compareWarpCoadd``.
2137 Usage of ``assembleCoadd.py`` expects a data reference to the tract patch
2138 and filter to be coadded (specified using
2139 '--id = [KEY=VALUE1[^VALUE2[^VALUE3...] [KEY=VALUE1[^VALUE2[^VALUE3...] ...]]')
2140 along
with a list of coaddTempExps to attempt to coadd (specified using
2141 '--selectId [KEY=VALUE1[^VALUE2[^VALUE3...] [KEY=VALUE1[^VALUE2[^VALUE3...] ...]]').
2142 Only the warps that cover the specified tract
and patch will be coadded.
2143 A list of the available optional arguments can be obtained by calling
2144 ``assembleCoadd.py``
with the ``--help`` command line argument:
2146 .. code-block:: none
2148 assembleCoadd.py --help
2150 To demonstrate usage of the ``CompareWarpAssembleCoaddTask``
in the larger
2151 context of multi-band processing, we will generate the HSC-I & -R band
2152 oadds
from HSC engineering test data provided
in the ``ci_hsc`` package.
2153 To begin, assuming that the lsst stack has been already set up, we must
2154 set up the ``obs_subaru``
and ``ci_hsc`` packages.
2155 This defines the environment variable ``$CI_HSC_DIR``
and points at the
2156 location of the package. The raw HSC data live
in the ``$CI_HSC_DIR/raw``
2157 directory. To begin assembling the coadds, we must first
2160 process the individual ccds
in $CI_HSC_RAW to produce calibrated exposures
2162 create a skymap that covers the area of the sky present
in the raw exposures
2164 warp the individual calibrated exposures to the tangent plane of the coadd
2166 We can perform all of these steps by running
2168 .. code-block:: none
2170 $CI_HSC_DIR scons warp-903986 warp-904014 warp-903990 warp-904010 warp-903988
2172 This will produce warped ``coaddTempExps``
for each visit. To coadd the
2173 warped data, we call ``assembleCoadd.py``
as follows:
2175 .. code-block:: none
2177 assembleCoadd.py --compareWarpCoadd $CI_HSC_DIR/DATA --id patch=5,4 tract=0 filter=HSC-I \
2178 --selectId visit=903986 ccd=16 --selectId visit=903986 ccd=22 --selectId visit=903986 ccd=23 \
2179 --selectId visit=903986 ccd=100 --selectId visit=904014 ccd=1 --selectId visit=904014 ccd=6 \
2180 --selectId visit=904014 ccd=12 --selectId visit=903990 ccd=18 --selectId visit=903990 ccd=25 \
2181 --selectId visit=904010 ccd=4 --selectId visit=904010 ccd=10 --selectId visit=904010 ccd=100 \
2182 --selectId visit=903988 ccd=16 --selectId visit=903988 ccd=17 --selectId visit=903988 ccd=23 \
2183 --selectId visit=903988 ccd=24
2185 This will process the HSC-I band data. The results are written
in
2186 ``$CI_HSC_DIR/DATA/deepCoadd-results/HSC-I``.
2188 ConfigClass = CompareWarpAssembleCoaddConfig
2189 _DefaultName = "compareWarpAssembleCoadd"
2192 AssembleCoaddTask.__init__(self, *args, **kwargs)
2193 self.makeSubtask(
"assembleStaticSkyModel")
2194 detectionSchema = afwTable.SourceTable.makeMinimalSchema()
2195 self.makeSubtask(
"detect", schema=detectionSchema)
2196 if self.config.doPreserveContainedBySource:
2197 self.makeSubtask(
"detectTemplate", schema=afwTable.SourceTable.makeMinimalSchema())
2198 if self.config.doScaleWarpVariance:
2199 self.makeSubtask(
"scaleWarpVariance")
2200 if self.config.doFilterMorphological:
2201 self.makeSubtask(
"maskStreaks")
2203 @utils.inheritDoc(AssembleCoaddTask)
2206 Generate a templateCoadd to use as a naive model of static sky to
2207 subtract
from PSF-Matched warps.
2211 result : `lsst.pipe.base.Struct`
2212 Result struct
with components:
2218 staticSkyModelInputRefs = copy.deepcopy(inputRefs)
2219 staticSkyModelInputRefs.inputWarps = inputRefs.psfMatchedWarps
2223 staticSkyModelOutputRefs = copy.deepcopy(outputRefs)
2224 if self.config.assembleStaticSkyModel.doWrite:
2225 staticSkyModelOutputRefs.coaddExposure = staticSkyModelOutputRefs.templateCoadd
2228 del outputRefs.templateCoadd
2229 del staticSkyModelOutputRefs.templateCoadd
2232 if 'nImage' in staticSkyModelOutputRefs.keys():
2233 del staticSkyModelOutputRefs.nImage
2235 templateCoadd = self.assembleStaticSkyModel.runQuantum(butlerQC, staticSkyModelInputRefs,
2236 staticSkyModelOutputRefs)
2237 if templateCoadd
is None:
2238 raise RuntimeError(self.
_noTemplateMessage_noTemplateMessage(self.assembleStaticSkyModel.warpType))
2240 return pipeBase.Struct(templateCoadd=templateCoadd.coaddExposure,
2241 nImage=templateCoadd.nImage,
2242 warpRefList=templateCoadd.warpRefList,
2243 imageScalerList=templateCoadd.imageScalerList,
2244 weightList=templateCoadd.weightList)
2246 @utils.inheritDoc(AssembleCoaddTask)
2249 Generate a templateCoadd to use as a naive model of static sky to
2250 subtract
from PSF-Matched warps.
2254 result : `lsst.pipe.base.Struct`
2255 Result struct
with components:
2260 templateCoadd = self.assembleStaticSkyModel.runDataRef(dataRef, selectDataList, warpRefList)
2261 if templateCoadd
is None:
2262 raise RuntimeError(self.
_noTemplateMessage_noTemplateMessage(self.assembleStaticSkyModel.warpType))
2264 return pipeBase.Struct(templateCoadd=templateCoadd.coaddExposure,
2265 nImage=templateCoadd.nImage,
2266 warpRefList=templateCoadd.warpRefList,
2267 imageScalerList=templateCoadd.imageScalerList,
2268 weightList=templateCoadd.weightList)
2270 def _noTemplateMessage(self, warpType):
2271 warpName = (warpType[0].upper() + warpType[1:])
2272 message =
"""No %(warpName)s warps were found to build the template coadd which is
2273 required to run CompareWarpAssembleCoaddTask. To continue assembling this type of coadd,
2274 first either rerun makeCoaddTempExp
with config.make%(warpName)s=
True or
2275 coaddDriver
with config.makeCoadTempExp.make%(warpName)s=
True, before assembleCoadd.
2277 Alternatively, to use another algorithm
with existing warps, retarget the CoaddDriverConfig to
2278 another algorithm like:
2281 config.assemble.retarget(SafeClipAssembleCoaddTask)
2282 """ % {"warpName": warpName}
2285 @utils.inheritDoc(AssembleCoaddTask)
2287 def run(self, skyInfo, tempExpRefList, imageScalerList, weightList,
2288 supplementaryData, *args, **kwargs):
2289 """Assemble the coadd.
2291 Find artifacts and apply them to the warps
' masks creating a list of
2292 alternative masks with a new
"CLIPPED" plane
and updated
"NO_DATA"
2293 plane. Then
pass these alternative masks to the base
class's `run`
2296 The input parameters ``supplementaryData`` is a `lsst.pipe.base.Struct`
2297 that must contain a ``templateCoadd`` that serves
as the
2298 model of the static sky.
2304 dataIds = [ref.dataId
for ref
in tempExpRefList]
2305 psfMatchedDataIds = [ref.dataId
for ref
in supplementaryData.warpRefList]
2307 if dataIds != psfMatchedDataIds:
2308 self.log.
info(
"Reordering and or/padding PSF-matched visit input list")
2309 supplementaryData.warpRefList =
reorderAndPadList(supplementaryData.warpRefList,
2310 psfMatchedDataIds, dataIds)
2311 supplementaryData.imageScalerList =
reorderAndPadList(supplementaryData.imageScalerList,
2312 psfMatchedDataIds, dataIds)
2315 spanSetMaskList = self.
findArtifactsfindArtifacts(supplementaryData.templateCoadd,
2316 supplementaryData.warpRefList,
2317 supplementaryData.imageScalerList)
2319 badMaskPlanes = self.config.badMaskPlanes[:]
2320 badMaskPlanes.append(
"CLIPPED")
2321 badPixelMask = afwImage.Mask.getPlaneBitMask(badMaskPlanes)
2323 result = AssembleCoaddTask.run(self, skyInfo, tempExpRefList, imageScalerList, weightList,
2324 spanSetMaskList, mask=badPixelMask)
2328 self.
applyAltEdgeMaskapplyAltEdgeMask(result.coaddExposure.maskedImage.mask, spanSetMaskList)
2332 """Propagate alt EDGE mask to SENSOR_EDGE AND INEXACT_PSF planes.
2338 altMaskList : `list`
2339 List of Dicts containing ``spanSet`` lists.
2340 Each element contains the new mask plane name (e.g. "CLIPPED
2341 and/
or "NO_DATA")
as the key,
and list of ``SpanSets`` to apply to
2344 maskValue = mask.getPlaneBitMask(["SENSOR_EDGE",
"INEXACT_PSF"])
2345 for visitMask
in altMaskList:
2346 if "EDGE" in visitMask:
2347 for spanSet
in visitMask[
'EDGE']:
2348 spanSet.clippedTo(mask.getBBox()).setMask(mask, maskValue)
2353 Loop through warps twice. The first loop builds a map with the count
2354 of how many epochs each pixel deviates
from the templateCoadd by more
2355 than ``config.chiThreshold`` sigma. The second loop takes each
2356 difference image
and filters the artifacts detected
in each using
2357 count map to filter out variable sources
and sources that are
2358 difficult to subtract cleanly.
2363 Exposure to serve
as model of static sky.
2364 tempExpRefList : `list`
2365 List of data references to warps.
2366 imageScalerList : `list`
2367 List of image scalers.
2372 List of dicts containing information about CLIPPED
2373 (i.e., artifacts), NO_DATA,
and EDGE pixels.
2376 self.log.debug("Generating Count Image, and mask lists.")
2377 coaddBBox = templateCoadd.getBBox()
2378 slateIm = afwImage.ImageU(coaddBBox)
2379 epochCountImage = afwImage.ImageU(coaddBBox)
2380 nImage = afwImage.ImageU(coaddBBox)
2381 spanSetArtifactList = []
2382 spanSetNoDataMaskList = []
2383 spanSetEdgeList = []
2384 spanSetBadMorphoList = []
2385 badPixelMask = self.getBadPixelMask()
2388 templateCoadd.mask.clearAllMaskPlanes()
2390 if self.config.doPreserveContainedBySource:
2391 templateFootprints = self.detectTemplate.detectFootprints(templateCoadd)
2393 templateFootprints =
None
2395 for warpRef, imageScaler
in zip(tempExpRefList, imageScalerList):
2397 if warpDiffExp
is not None:
2399 nImage.array += (numpy.isfinite(warpDiffExp.image.array)
2400 * ((warpDiffExp.mask.array & badPixelMask) == 0)).astype(numpy.uint16)
2401 fpSet = self.detect.detectFootprints(warpDiffExp, doSmooth=
False, clearMask=
True)
2402 fpSet.positive.merge(fpSet.negative)
2403 footprints = fpSet.positive
2405 spanSetList = [footprint.spans
for footprint
in footprints.getFootprints()]
2408 if self.config.doPrefilterArtifacts:
2412 self.detect.clearMask(warpDiffExp.mask)
2413 for spans
in spanSetList:
2414 spans.setImage(slateIm, 1, doClip=
True)
2415 spans.setMask(warpDiffExp.mask, warpDiffExp.mask.getPlaneBitMask(
"DETECTED"))
2416 epochCountImage += slateIm
2418 if self.config.doFilterMorphological:
2419 maskName = self.config.streakMaskName
2420 _ = self.maskStreaks.
run(warpDiffExp)
2421 streakMask = warpDiffExp.mask
2422 spanSetStreak = afwGeom.SpanSet.fromMask(streakMask,
2423 streakMask.getPlaneBitMask(maskName)).split()
2425 psf = warpDiffExp.getPsf()
2426 for s, sset
in enumerate(spanSetStreak):
2427 psfShape = psf.computeShape(sset.computeCentroid())
2428 dilation = self.config.growStreakFp * psfShape.getDeterminantRadius()
2429 sset_dilated = sset.dilated(
int(dilation))
2430 spanSetStreak[s] = sset_dilated
2436 nans = numpy.where(numpy.isnan(warpDiffExp.maskedImage.image.array), 1, 0)
2438 nansMask.setXY0(warpDiffExp.getXY0())
2439 edgeMask = warpDiffExp.mask
2440 spanSetEdgeMask = afwGeom.SpanSet.fromMask(edgeMask,
2441 edgeMask.getPlaneBitMask(
"EDGE")).split()
2445 nansMask = afwImage.MaskX(coaddBBox, 1)
2447 spanSetEdgeMask = []
2450 spanSetNoDataMask = afwGeom.SpanSet.fromMask(nansMask).split()
2452 spanSetNoDataMaskList.append(spanSetNoDataMask)
2453 spanSetArtifactList.append(spanSetList)
2454 spanSetEdgeList.append(spanSetEdgeMask)
2455 if self.config.doFilterMorphological:
2456 spanSetBadMorphoList.append(spanSetStreak)
2459 path = self.
_dataRef2DebugPath_dataRef2DebugPath(
"epochCountIm", tempExpRefList[0], coaddLevel=
True)
2460 epochCountImage.writeFits(path)
2462 for i, spanSetList
in enumerate(spanSetArtifactList):
2464 filteredSpanSetList = self.
filterArtifactsfilterArtifacts(spanSetList, epochCountImage, nImage,
2466 spanSetArtifactList[i] = filteredSpanSetList
2467 if self.config.doFilterMorphological:
2468 spanSetArtifactList[i] += spanSetBadMorphoList[i]
2471 for artifacts, noData, edge
in zip(spanSetArtifactList, spanSetNoDataMaskList, spanSetEdgeList):
2472 altMasks.append({
'CLIPPED': artifacts,
2478 """Remove artifact candidates covered by bad mask plane.
2480 Any future editing of the candidate list that does not depend on
2481 temporal information should go
in this method.
2485 spanSetList : `list`
2486 List of SpanSets representing artifact candidates.
2488 Exposure containing mask planes used to prefilter.
2492 returnSpanSetList : `list`
2493 List of SpanSets
with artifacts.
2495 badPixelMask = exp.mask.getPlaneBitMask(self.config.prefilterArtifactsMaskPlanes)
2496 goodArr = (exp.mask.array & badPixelMask) == 0
2497 returnSpanSetList = []
2498 bbox = exp.getBBox()
2499 x0, y0 = exp.getXY0()
2500 for i, span
in enumerate(spanSetList):
2501 y, x = span.clippedTo(bbox).indices()
2502 yIndexLocal = numpy.array(y) - y0
2503 xIndexLocal = numpy.array(x) - x0
2504 goodRatio = numpy.count_nonzero(goodArr[yIndexLocal, xIndexLocal])/span.getArea()
2505 if goodRatio > self.config.prefilterArtifactsRatio:
2506 returnSpanSetList.append(span)
2507 return returnSpanSetList
2509 def filterArtifacts(self, spanSetList, epochCountImage, nImage, footprintsToExclude=None):
2510 """Filter artifact candidates.
2514 spanSetList : `list`
2515 List of SpanSets representing artifact candidates.
2517 Image of accumulated number of warpDiff detections.
2519 Image of the accumulated number of total epochs contributing.
2523 maskSpanSetList : `list`
2524 List of SpanSets with artifacts.
2527 maskSpanSetList = []
2528 x0, y0 = epochCountImage.getXY0()
2529 for i, span
in enumerate(spanSetList):
2530 y, x = span.indices()
2531 yIdxLocal = [y1 - y0
for y1
in y]
2532 xIdxLocal = [x1 - x0
for x1
in x]
2533 outlierN = epochCountImage.array[yIdxLocal, xIdxLocal]
2534 totalN = nImage.array[yIdxLocal, xIdxLocal]
2537 effMaxNumEpochsHighN = (self.config.maxNumEpochs
2538 + self.config.maxFractionEpochsHigh*numpy.mean(totalN))
2539 effMaxNumEpochsLowN = self.config.maxFractionEpochsLow * numpy.mean(totalN)
2540 effectiveMaxNumEpochs =
int(
min(effMaxNumEpochsLowN, effMaxNumEpochsHighN))
2541 nPixelsBelowThreshold = numpy.count_nonzero((outlierN > 0)
2542 & (outlierN <= effectiveMaxNumEpochs))
2543 percentBelowThreshold = nPixelsBelowThreshold / len(outlierN)
2544 if percentBelowThreshold > self.config.spatialThreshold:
2545 maskSpanSetList.append(span)
2547 if self.config.doPreserveContainedBySource
and footprintsToExclude
is not None:
2549 filteredMaskSpanSetList = []
2550 for span
in maskSpanSetList:
2552 for footprint
in footprintsToExclude.positive.getFootprints():
2553 if footprint.spans.contains(span):
2557 filteredMaskSpanSetList.append(span)
2558 maskSpanSetList = filteredMaskSpanSetList
2560 return maskSpanSetList
2562 def _readAndComputeWarpDiff(self, warpRef, imageScaler, templateCoadd):
2563 """Fetch a warp from the butler and return a warpDiff.
2568 Butler dataRef for the warp.
2570 An image scaler object.
2572 Exposure to be substracted
from the scaled warp.
2577 Exposure of the image difference between the warp
and template.
2585 warpName = self.getTempExpDatasetName(
'psfMatched')
2586 if not isinstance(warpRef, DeferredDatasetHandle):
2587 if not warpRef.datasetExists(warpName):
2588 self.log.
warning(
"Could not find %s %s; skipping it", warpName, warpRef.dataId)
2590 warp = warpRef.get(datasetType=warpName, immediate=
True)
2592 imageScaler.scaleMaskedImage(warp.getMaskedImage())
2593 mi = warp.getMaskedImage()
2594 if self.config.doScaleWarpVariance:
2596 self.scaleWarpVariance.
run(mi)
2597 except Exception
as exc:
2598 self.log.
warning(
"Unable to rescale variance of warp (%s); leaving it as-is", exc)
2599 mi -= templateCoadd.getMaskedImage()
2602 def _dataRef2DebugPath(self, prefix, warpRef, coaddLevel=False):
2603 """Return a path to which to write debugging output.
2605 Creates a hyphen-delimited string of dataId values for simple filenames.
2610 Prefix
for filename.
2612 Butler dataRef to make the path
from.
2613 coaddLevel : `bool`, optional.
2614 If
True, include only coadd-level keys (e.g.,
'tract',
'patch',
2615 'filter', but no
'visit').
2620 Path
for debugging output.
2623 keys = warpRef.getButler().getKeys(self.getCoaddDatasetName(self.warpType))
2625 keys = warpRef.dataId.keys()
2626 keyList = sorted(keys, reverse=
True)
2628 filename =
"%s-%s.fits" % (prefix,
'-'.join([
str(warpRef.dataId[k])
for k
in keyList]))
2629 return os.path.join(directory, filename)
std::vector< SchemaItem< Flag > > * items
A Threshold is used to pass a threshold value to detection algorithms.
A compact representation of a collection of pixels.
A class to contain the data, WCS, and other information needed to describe an image of the sky.
A group of labels for a filter in an exposure or coadd.
A class to represent a 2-dimensional array of pixels.
Represent a 2-dimensional array of bitmask pixels.
A class to manipulate images, masks, and variance as a single object.
Pass parameters to a Statistics object.
A floating-point coordinate rectangle geometry.
An integer coordinate rectangle.
Reports invalid arguments.
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.
daf::base::PropertyList * list
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects.
std::shared_ptr< lsst::afw::image::Image< PixelT > > statisticsStack(std::vector< std::shared_ptr< lsst::afw::image::Image< PixelT > > > &images, Property flags, StatisticsControl const &sctrl=StatisticsControl(), std::vector< lsst::afw::image::VariancePixel > const &wvector=std::vector< lsst::afw::image::VariancePixel >(0))
A function to compute some statistics of a stack of Images.
Statistics makeStatistics(lsst::afw::image::Image< Pixel > const &img, lsst::afw::image::Mask< image::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl=StatisticsControl())
Handle a watered-down front-end to the constructor (no variance)
Property stringToStatisticsProperty(std::string const property)
Conversion function to switch a string to a Property (see Statistics.h)
void setCoaddEdgeBits(lsst::afw::image::Mask< lsst::afw::image::MaskPixel > &coaddMask, lsst::afw::image::Image< WeightPixelT > const &weightMap)
set edge bits of coadd mask based on weight map
bool all(CoordinateExpr< N > const &expr) noexcept
Return true if all elements are true.
Fit spatial kernel using approximate fluxes for candidates, and solving a linear system of equations.
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
def reorderAndPadList(inputList, inputKeys, outputKeys, padWith=None)
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)
def makeCoaddSuffix(warpType="direct")
def makeSkyInfo(skyMap, tractId, patchId)
def getGroupDataRef(butler, datasetType, groupTuple, keys)
def groupPatchExposures(patchDataRef, calexpDataRefList, coaddDatasetType="deepCoadd", tempExpDatasetType="deepCoadd_directWarp")