37 from .coaddBase
import CoaddBaseTask, SelectDataIdContainer, makeSkyInfo
38 from .interpImage
import InterpImageTask
39 from .scaleZeroPoint
import ScaleZeroPointTask
40 from .coaddHelpers
import groupPatchExposures, getGroupDataRef
41 from .scaleVariance
import ScaleVarianceTask
45 __all__ = [
"AssembleCoaddTask",
"AssembleCoaddConfig",
"SafeClipAssembleCoaddTask",
46 "SafeClipAssembleCoaddConfig",
"CompareWarpAssembleCoaddTask",
"CompareWarpAssembleCoaddConfig"]
50 """Configuration parameters for the `AssembleCoaddTask`. 54 The `doMaskBrightObjects` and `brightObjectMaskName` configuration options 55 only set the bitplane config.brightObjectMaskName. To make this useful you 56 *must* also configure the flags.pixel algorithm, for example by adding 60 config.measurement.plugins["base_PixelFlags"].masksFpCenter.append("BRIGHT_OBJECT") 61 config.measurement.plugins["base_PixelFlags"].masksFpAnywhere.append("BRIGHT_OBJECT") 63 to your measureCoaddSources.py and forcedPhotCoadd.py config overrides. 65 warpType = pexConfig.Field(
66 doc=
"Warp name: one of 'direct' or 'psfMatched'",
70 subregionSize = pexConfig.ListField(
72 doc=
"Width, height of stack subregion size; " 73 "make small enough that a full stack of images will fit into memory at once.",
77 statistic = pexConfig.Field(
79 doc=
"Main stacking statistic for aggregating over the epochs.",
82 doSigmaClip = pexConfig.Field(
84 doc=
"Perform sigma clipped outlier rejection with MEANCLIP statistic? (DEPRECATED)",
87 sigmaClip = pexConfig.Field(
89 doc=
"Sigma for outlier rejection; ignored if non-clipping statistic selected.",
92 clipIter = pexConfig.Field(
94 doc=
"Number of iterations of outlier rejection; ignored if non-clipping statistic selected.",
97 calcErrorFromInputVariance = pexConfig.Field(
99 doc=
"Calculate coadd variance from input variance by stacking statistic." 100 "Passed to StatisticsControl.setCalcErrorFromInputVariance()",
103 scaleZeroPoint = pexConfig.ConfigurableField(
104 target=ScaleZeroPointTask,
105 doc=
"Task to adjust the photometric zero point of the coadd temp exposures",
107 doInterp = pexConfig.Field(
108 doc=
"Interpolate over NaN pixels? Also extrapolate, if necessary, but the results are ugly.",
112 interpImage = pexConfig.ConfigurableField(
113 target=InterpImageTask,
114 doc=
"Task to interpolate (and extrapolate) over NaN pixels",
116 doWrite = pexConfig.Field(
117 doc=
"Persist coadd?",
121 doNImage = pexConfig.Field(
122 doc=
"Create image of number of contributing exposures for each pixel",
126 doUsePsfMatchedPolygons = pexConfig.Field(
127 doc=
"Use ValidPolygons from shrunk Psf-Matched Calexps? Should be set to True by CompareWarp only.",
131 maskPropagationThresholds = pexConfig.DictField(
134 doc=(
"Threshold (in fractional weight) of rejection at which we propagate a mask plane to " 135 "the coadd; that is, we set the mask bit on the coadd if the fraction the rejected frames " 136 "would have contributed exceeds this value."),
137 default={
"SAT": 0.1},
139 removeMaskPlanes = pexConfig.ListField(dtype=str, default=[
"NOT_DEBLENDED"],
140 doc=
"Mask planes to remove before coadding")
141 doMaskBrightObjects = pexConfig.Field(dtype=bool, default=
False,
142 doc=
"Set mask and flag bits for bright objects?")
143 brightObjectMaskName = pexConfig.Field(dtype=str, default=
"BRIGHT_OBJECT",
144 doc=
"Name of mask bit used for bright objects")
145 coaddPsf = pexConfig.ConfigField(
146 doc=
"Configuration for CoaddPsf",
147 dtype=measAlg.CoaddPsfConfig,
149 doAttachTransmissionCurve = pexConfig.Field(
150 dtype=bool, default=
False, optional=
False,
151 doc=(
"Attach a piecewise TransmissionCurve for the coadd? " 152 "(requires all input Exposures to have TransmissionCurves).")
154 inputWarps = pipeBase.InputDatasetField(
155 doc=(
"Input list of warps to be assemebled i.e. stacked." 156 "WarpType (e.g. direct, psfMatched) is controlled by we warpType config parameter"),
157 nameTemplate=
"{inputCoaddName}Coadd_{warpType}Warp",
158 storageClass=
"ExposureF",
159 dimensions=(
"tract",
"patch",
"skymap",
"visit",
"instrument"),
162 skyMap = pipeBase.InputDatasetField(
163 doc=
"Input definition of geometry/bbox and projection/wcs for coadded exposures",
164 nameTemplate=
"{inputCoaddName}Coadd_skyMap",
165 storageClass=
"SkyMap",
166 dimensions=(
"skymap", ),
169 brightObjectMask = pipeBase.InputDatasetField(
170 doc=(
"Input Bright Object Mask mask produced with external catalogs to be applied to the mask plane" 172 name=
"brightObjectMask",
173 storageClass=
"ObjectMaskCatalog",
174 dimensions=(
"tract",
"patch",
"skymap",
"abstract_filter"),
177 coaddExposure = pipeBase.OutputDatasetField(
178 doc=
"Output coadded exposure, produced by stacking input warps",
179 nameTemplate=
"{outputCoaddName}Coadd",
180 storageClass=
"ExposureF",
181 dimensions=(
"tract",
"patch",
"skymap",
"abstract_filter"),
184 nImage = pipeBase.OutputDatasetField(
185 doc=
"Output image of number of input images per pixel",
186 nameTemplate=
"{outputCoaddName}Coadd_nImage",
187 storageClass=
"ImageU",
188 dimensions=(
"tract",
"patch",
"skymap",
"abstract_filter"),
192 hasFakes = pexConfig.Field(
195 doc=
"Should be set to True if fake sources have been inserted into the input data." 201 self.formatTemplateNames({
"inputCoaddName":
"deep",
"outputCoaddName":
"deep",
203 self.quantum.dimensions = (
"tract",
"patch",
"abstract_filter",
"skymap")
210 log.warn(
"Config doPsfMatch deprecated. Setting warpType='psfMatched'")
213 log.warn(
'doSigmaClip deprecated. To replicate behavior, setting statistic to "MEANCLIP"')
215 if self.
doInterp and self.
statistic not in [
'MEAN',
'MEDIAN',
'MEANCLIP',
'VARIANCE',
'VARIANCECLIP']:
216 raise ValueError(
"Must set doInterp=False for statistic=%s, which does not " 217 "compute and set a non-zero coadd variance estimate." % (self.
statistic))
219 unstackableStats = [
'NOTHING',
'ERROR',
'ORMASK']
220 if not hasattr(afwMath.Property, self.
statistic)
or self.
statistic in unstackableStats:
221 stackableStats = [
str(k)
for k
in afwMath.Property.__members__.keys()
222 if str(k)
not in unstackableStats]
223 raise ValueError(
"statistic %s is not allowed. Please choose one of %s." 228 """Assemble a coadded image from a set of warps (coadded temporary exposures). 230 We want to assemble a coadded image from a set of Warps (also called 231 coadded temporary exposures or ``coaddTempExps``). 232 Each input Warp covers a patch on the sky and corresponds to a single 233 run/visit/exposure of the covered patch. We provide the task with a list 234 of Warps (``selectDataList``) from which it selects Warps that cover the 235 specified patch (pointed at by ``dataRef``). 236 Each Warp that goes into a coadd will typically have an independent 237 photometric zero-point. Therefore, we must scale each Warp to set it to 238 a common photometric zeropoint. WarpType may be one of 'direct' or 239 'psfMatched', and the boolean configs `config.makeDirect` and 240 `config.makePsfMatched` set which of the warp types will be coadded. 241 The coadd is computed as a mean with optional outlier rejection. 242 Criteria for outlier rejection are set in `AssembleCoaddConfig`. 243 Finally, Warps can have bad 'NaN' pixels which received no input from the 244 source calExps. We interpolate over these bad (NaN) pixels. 246 `AssembleCoaddTask` uses several sub-tasks. These are 248 - `ScaleZeroPointTask` 249 - create and use an ``imageScaler`` object to scale the photometric zeropoint for each Warp 251 - interpolate across bad pixels (NaN) in the final coadd 253 You can retarget these subtasks if you wish. 257 The `lsst.pipe.base.cmdLineTask.CmdLineTask` interface supports a 258 flag ``-d`` to import ``debug.py`` from your ``PYTHONPATH``; see 259 `baseDebug` for more about ``debug.py`` files. `AssembleCoaddTask` has 260 no debug variables of its own. Some of the subtasks may support debug 261 variables. See the documentation for the subtasks for further information. 265 `AssembleCoaddTask` assembles a set of warped images into a coadded image. 266 The `AssembleCoaddTask` can be invoked by running ``assembleCoadd.py`` 267 with the flag '--legacyCoadd'. Usage of assembleCoadd.py expects two 268 inputs: a data reference to the tract patch and filter to be coadded, and 269 a list of Warps to attempt to coadd. These are specified using ``--id`` and 270 ``--selectId``, respectively: 274 --id = [KEY=VALUE1[^VALUE2[^VALUE3...] [KEY=VALUE1[^VALUE2[^VALUE3...] ...]] 275 --selectId [KEY=VALUE1[^VALUE2[^VALUE3...] [KEY=VALUE1[^VALUE2[^VALUE3...] ...]] 277 Only the Warps that cover the specified tract and patch will be coadded. 278 A list of the available optional arguments can be obtained by calling 279 ``assembleCoadd.py`` with the ``--help`` command line argument: 283 assembleCoadd.py --help 285 To demonstrate usage of the `AssembleCoaddTask` in the larger context of 286 multi-band processing, we will generate the HSC-I & -R band coadds from 287 HSC engineering test data provided in the ``ci_hsc`` package. To begin, 288 assuming that the lsst stack has been already set up, we must set up the 289 obs_subaru and ``ci_hsc`` packages. This defines the environment variable 290 ``$CI_HSC_DIR`` and points at the location of the package. The raw HSC 291 data live in the ``$CI_HSC_DIR/raw directory``. To begin assembling the 292 coadds, we must first 295 - process the individual ccds in $CI_HSC_RAW to produce calibrated exposures 297 - create a skymap that covers the area of the sky present in the raw exposures 299 - warp the individual calibrated exposures to the tangent plane of the coadd 301 We can perform all of these steps by running 305 $CI_HSC_DIR scons warp-903986 warp-904014 warp-903990 warp-904010 warp-903988 307 This will produce warped exposures for each visit. To coadd the warped 308 data, we call assembleCoadd.py as follows: 312 assembleCoadd.py --legacyCoadd $CI_HSC_DIR/DATA --id patch=5,4 tract=0 filter=HSC-I \ 313 --selectId visit=903986 ccd=16 --selectId visit=903986 ccd=22 --selectId visit=903986 ccd=23 \ 314 --selectId visit=903986 ccd=100 --selectId visit=904014 ccd=1 --selectId visit=904014 ccd=6 \ 315 --selectId visit=904014 ccd=12 --selectId visit=903990 ccd=18 --selectId visit=903990 ccd=25 \ 316 --selectId visit=904010 ccd=4 --selectId visit=904010 ccd=10 --selectId visit=904010 ccd=100 \ 317 --selectId visit=903988 ccd=16 --selectId visit=903988 ccd=17 --selectId visit=903988 ccd=23 \ 318 --selectId visit=903988 ccd=24 320 that will process the HSC-I band data. The results are written in 321 ``$CI_HSC_DIR/DATA/deepCoadd-results/HSC-I``. 323 You may also choose to run: 327 scons warp-903334 warp-903336 warp-903338 warp-903342 warp-903344 warp-903346 328 assembleCoadd.py --legacyCoadd $CI_HSC_DIR/DATA --id patch=5,4 tract=0 filter=HSC-R \ 329 --selectId visit=903334 ccd=16 --selectId visit=903334 ccd=22 --selectId visit=903334 ccd=23 \ 330 --selectId visit=903334 ccd=100 --selectId visit=903336 ccd=17 --selectId visit=903336 ccd=24 \ 331 --selectId visit=903338 ccd=18 --selectId visit=903338 ccd=25 --selectId visit=903342 ccd=4 \ 332 --selectId visit=903342 ccd=10 --selectId visit=903342 ccd=100 --selectId visit=903344 ccd=0 \ 333 --selectId visit=903344 ccd=5 --selectId visit=903344 ccd=11 --selectId visit=903346 ccd=1 \ 334 --selectId visit=903346 ccd=6 --selectId visit=903346 ccd=12 336 to generate the coadd for the HSC-R band if you are interested in 337 following multiBand Coadd processing as discussed in `pipeTasks_multiBand` 338 (but note that normally, one would use the `SafeClipAssembleCoaddTask` 339 rather than `AssembleCoaddTask` to make the coadd. 341 ConfigClass = AssembleCoaddConfig
342 _DefaultName =
"assembleCoadd" 347 argNames = [
"config",
"name",
"parentTask",
"log"]
348 kwargs.update({k: v
for k, v
in zip(argNames, args)})
349 warnings.warn(
"AssembleCoadd received positional args, and casting them as kwargs: %s. " 350 "PipelineTask will not take positional args" % argNames, FutureWarning)
353 self.makeSubtask(
"interpImage")
354 self.makeSubtask(
"scaleZeroPoint")
356 if self.config.doMaskBrightObjects:
360 except pexExceptions.LsstCppException:
361 raise RuntimeError(
"Unable to define mask plane for bright objects; planes used are %s" %
362 mask.getMaskPlaneDict().
keys())
369 """Return output dataset type descriptors 371 Remove output dataset types not produced by the Task 374 if not config.doNImage:
375 outputTypeDict.pop(
"nImage",
None)
376 return outputTypeDict
380 """Return input dataset type descriptors 382 Remove input dataset types not used by the Task 385 if not config.doMaskBrightObjects:
386 inputTypeDict.pop(
"brightObjectMask",
None)
391 return frozenset([
"brightObjectMask"])
394 """Assemble a coadd from a set of Warps. 396 PipelineTask (Gen3) entry point to Coadd a set of Warps. 397 Analogous to `runDataRef`, it prepares all the data products to be 398 passed to `run`, and processes the results before returning to struct 399 of results to be written out. AssembleCoadd cannot fit all Warps in memory. 400 Therefore, its inputs are accessed subregion by subregion 401 by the `lsst.daf.butler.ShimButler` that quacks like a Gen2 402 `lsst.daf.persistence.Butler`. Updates to this method should 403 correspond to an update in `runDataRef` while both entry points 409 Keys are the names of the configs describing input dataset types. 410 Values are input Python-domain data objects (or lists of objects) 411 retrieved from data butler. 412 inputDataIds : `dict` 413 Keys are the names of the configs describing input dataset types. 414 Values are DataIds (or lists of DataIds) that task consumes for 415 corresponding dataset type. 416 outputDataIds : `dict` 417 Keys are the names of the configs describing input dataset types. 418 Values are DataIds (or lists of DataIds) that task is to produce 419 for corresponding dataset type. 420 butler : `lsst.daf.butler.Butler` 421 Gen3 Butler object for fetching additional data products before 426 result : `lsst.pipe.base.Struct` 427 Result struct with components: 429 - ``coaddExposure`` : coadded exposure (``lsst.afw.image.Exposure``) 430 - ``nImage``: N Image (``lsst.afw.image.Image``) 434 skyMap = inputData[
"skyMap"]
435 outputDataId = next(iter(outputDataIds.values()))
437 tractId=outputDataId[
'tract'],
438 patchId=outputDataId[
'patch'])
442 warpRefList = [butlerShim.dataRef(self.config.inputWarps.name, dataId=dataId)
443 for dataId
in inputDataIds[
'inputWarps']]
446 patchRef = butlerShim.dataRef(self.config.coaddExposure.name, dataId=outputDataIds[
'coaddExposure'])
450 self.log.
info(
"Found %d %s", len(inputs.tempExpRefList),
452 if len(inputs.tempExpRefList) == 0:
453 self.log.
warn(
"No coadd temporary exposures found")
458 retStruct = self.
run(inputData[
'skyInfo'], inputs.tempExpRefList, inputs.imageScalerList,
459 inputs.weightList, supplementaryData=supplementaryData)
465 def runDataRef(self, dataRef, selectDataList=None, warpRefList=None):
466 """Assemble a coadd from a set of Warps. 468 Pipebase.CmdlineTask entry point to Coadd a set of Warps. 469 Compute weights to be applied to each Warp and 470 find scalings to match the photometric zeropoint to a reference Warp. 471 Assemble the Warps using `run`. Interpolate over NaNs and 472 optionally write the coadd to disk. Return the coadded exposure. 476 dataRef : `lsst.daf.persistence.butlerSubset.ButlerDataRef` 477 Data reference defining the patch for coaddition and the 478 reference Warp (if ``config.autoReference=False``). 479 Used to access the following data products: 480 - ``self.config.coaddName + "Coadd_skyMap"`` 481 - ``self.config.coaddName + "Coadd_ + <warpType> + "Warp"`` (optionally) 482 - ``self.config.coaddName + "Coadd"`` 483 selectDataList : `list` 484 List of data references to Calexps. Data to be coadded will be 485 selected from this list based on overlap with the patch defined 486 by dataRef, grouped by visit, and converted to a list of data 489 List of data references to Warps to be coadded. 490 Note: `warpRefList` is just the new name for `tempExpRefList`. 494 retStruct : `lsst.pipe.base.Struct` 495 Result struct with components: 497 - ``coaddExposure``: coadded exposure (``Exposure``). 498 - ``nImage``: exposure count image (``Image``). 500 if selectDataList
and warpRefList:
501 raise RuntimeError(
"runDataRef received both a selectDataList and warpRefList, " 502 "and which to use is ambiguous. Please pass only one.")
505 if warpRefList
is None:
506 calExpRefList = self.
selectExposures(dataRef, skyInfo, selectDataList=selectDataList)
507 if len(calExpRefList) == 0:
508 self.log.
warn(
"No exposures to coadd")
510 self.log.
info(
"Coadding %d exposures", len(calExpRefList))
515 self.log.
info(
"Found %d %s", len(inputData.tempExpRefList),
517 if len(inputData.tempExpRefList) == 0:
518 self.log.
warn(
"No coadd temporary exposures found")
523 retStruct = self.
run(skyInfo, inputData.tempExpRefList, inputData.imageScalerList,
524 inputData.weightList, supplementaryData=supplementaryData)
527 if self.config.doWrite:
532 self.log.
info(
"Persisting %s" % coaddDatasetName)
533 dataRef.put(retStruct.coaddExposure, coaddDatasetName)
534 if self.config.doNImage
and retStruct.nImage
is not None:
540 """Interpolate over missing data and mask bright stars. 544 coaddExposure : `lsst.afw.image.Exposure` 545 The coadded exposure to process. 546 dataRef : `lsst.daf.persistence.ButlerDataRef` 547 Butler data reference for supplementary data. 549 if self.config.doInterp:
550 self.interpImage.
run(coaddExposure.getMaskedImage(), planeName=
"NO_DATA")
552 varArray = coaddExposure.variance.array
553 with numpy.errstate(invalid=
"ignore"):
554 varArray[:] = numpy.where(varArray > 0, varArray, numpy.inf)
556 if self.config.doMaskBrightObjects:
561 """Make additional inputs to run() specific to subclasses (Gen2) 563 Duplicates interface of `runDataRef` method 564 Available to be implemented by subclasses only if they need the 565 coadd dataRef for performing preliminary processing before 566 assembling the coadd. 570 dataRef : `lsst.daf.persistence.ButlerDataRef` 571 Butler data reference for supplementary data. 572 selectDataList : `list` 573 List of data references to Warps. 578 """Make additional inputs to run() specific to subclasses (Gen3) 580 Duplicates interface of`adaptArgsAndRun` method. 581 Available to be implemented by subclasses only if they need the 582 coadd dataRef for performing preliminary processing before 583 assembling the coadd. 588 Keys are the names of the configs describing input dataset types. 589 Values are input Python-domain data objects (or lists of objects) 590 retrieved from data butler. 591 inputDataIds : `dict` 592 Keys are the names of the configs describing input dataset types. 593 Values are DataIds (or lists of DataIds) that task consumes for 594 corresponding dataset type. 595 DataIds are guaranteed to match data objects in ``inputData``. 596 outputDataIds : `dict` 597 Keys are the names of the configs describing input dataset types. 598 Values are DataIds (or lists of DataIds) that task is to produce 599 for corresponding dataset type. 600 butler : `lsst.daf.butler.Butler` 601 Gen3 Butler object for fetching additional data products before 606 result : `lsst.pipe.base.Struct` 607 Contains whatever additional data the subclass's `run` method needs 612 """Generate list data references corresponding to warped exposures 613 that lie within the patch to be coadded. 618 Data reference for patch. 619 calExpRefList : `list` 620 List of data references for input calexps. 624 tempExpRefList : `list` 625 List of Warp/CoaddTempExp data references. 627 butler = patchRef.getButler()
628 groupData =
groupPatchExposures(patchRef, calExpRefList, self.getCoaddDatasetName(self.warpType),
629 self.getTempExpDatasetName(self.warpType))
630 tempExpRefList = [
getGroupDataRef(butler, self.getTempExpDatasetName(self.warpType),
631 g, groupData.keys)
for 632 g
in groupData.groups.keys()]
633 return tempExpRefList
636 """Prepare the input warps for coaddition by measuring the weight for 637 each warp and the scaling for the photometric zero point. 639 Each Warp has its own photometric zeropoint and background variance. 640 Before coadding these Warps together, compute a scale factor to 641 normalize the photometric zeropoint and compute the weight for each Warp. 646 List of data references to tempExp 650 result : `lsst.pipe.base.Struct` 651 Result struct with components: 653 - ``tempExprefList``: `list` of data references to tempExp. 654 - ``weightList``: `list` of weightings. 655 - ``imageScalerList``: `list` of image scalers. 658 statsCtrl.setNumSigmaClip(self.config.sigmaClip)
659 statsCtrl.setNumIter(self.config.clipIter)
661 statsCtrl.setNanSafe(
True)
669 for tempExpRef
in refList:
670 if not tempExpRef.datasetExists(tempExpName):
671 self.log.
warn(
"Could not find %s %s; skipping it", tempExpName, tempExpRef.dataId)
674 tempExp = tempExpRef.get(tempExpName, immediate=
True)
676 if numpy.isnan(tempExp.image.array).
all():
678 maskedImage = tempExp.getMaskedImage()
679 imageScaler = self.scaleZeroPoint.computeImageScaler(
684 imageScaler.scaleMaskedImage(maskedImage)
685 except Exception
as e:
686 self.log.
warn(
"Scaling failed for %s (skipping it): %s", tempExpRef.dataId, e)
689 afwMath.MEANCLIP, statsCtrl)
690 meanVar, meanVarErr = statObj.getResult(afwMath.MEANCLIP)
691 weight = 1.0 /
float(meanVar)
692 if not numpy.isfinite(weight):
693 self.log.
warn(
"Non-finite weight for %s: skipping", tempExpRef.dataId)
695 self.log.
info(
"Weight of %s %s = %0.3f", tempExpName, tempExpRef.dataId, weight)
700 tempExpRefList.append(tempExpRef)
701 weightList.append(weight)
702 imageScalerList.append(imageScaler)
704 return pipeBase.Struct(tempExpRefList=tempExpRefList, weightList=weightList,
705 imageScalerList=imageScalerList)
708 """Prepare the statistics for coadding images. 712 mask : `int`, optional 713 Bit mask value to exclude from coaddition. 717 stats : `lsst.pipe.base.Struct` 718 Statistics structure with the following fields: 720 - ``statsCtrl``: Statistics control object for coadd 721 (`lsst.afw.math.StatisticsControl`) 722 - ``statsFlags``: Statistic for coadd (`lsst.afw.math.Property`) 727 statsCtrl.setNumSigmaClip(self.config.sigmaClip)
728 statsCtrl.setNumIter(self.config.clipIter)
729 statsCtrl.setAndMask(mask)
730 statsCtrl.setNanSafe(
True)
731 statsCtrl.setWeighted(
True)
732 statsCtrl.setCalcErrorFromInputVariance(self.config.calcErrorFromInputVariance)
733 for plane, threshold
in self.config.maskPropagationThresholds.items():
734 bit = afwImage.Mask.getMaskPlane(plane)
735 statsCtrl.setMaskPropagationThreshold(bit, threshold)
737 return pipeBase.Struct(ctrl=statsCtrl, flags=statsFlags)
739 def run(self, skyInfo, tempExpRefList, imageScalerList, weightList,
740 altMaskList=None, mask=None, supplementaryData=None):
741 """Assemble a coadd from input warps 743 Assemble the coadd using the provided list of coaddTempExps. Since 744 the full coadd covers a patch (a large area), the assembly is 745 performed over small areas on the image at a time in order to 746 conserve memory usage. Iterate over subregions within the outer 747 bbox of the patch using `assembleSubregion` to stack the corresponding 748 subregions from the coaddTempExps with the statistic specified. 749 Set the edge bits the coadd mask based on the weight map. 753 skyInfo : `lsst.pipe.base.Struct` 754 Struct with geometric information about the patch. 755 tempExpRefList : `list` 756 List of data references to Warps (previously called CoaddTempExps). 757 imageScalerList : `list` 758 List of image scalers. 761 altMaskList : `list`, optional 762 List of alternate masks to use rather than those stored with 764 mask : `int`, optional 765 Bit mask value to exclude from coaddition. 766 supplementaryData : lsst.pipe.base.Struct, optional 767 Struct with additional data products needed to assemble coadd. 768 Only used by subclasses that implement `makeSupplementaryData` 773 result : `lsst.pipe.base.Struct` 774 Result struct with components: 776 - ``coaddExposure``: coadded exposure (``lsst.afw.image.Exposure``). 777 - ``nImage``: exposure count image (``lsst.afw.image.Image``). 780 self.log.
info(
"Assembling %s %s", len(tempExpRefList), tempExpName)
783 if altMaskList
is None:
784 altMaskList = [
None]*len(tempExpRefList)
786 coaddExposure = afwImage.ExposureF(skyInfo.bbox, skyInfo.wcs)
787 coaddExposure.setPhotoCalib(self.scaleZeroPoint.getPhotoCalib())
788 coaddExposure.getInfo().setCoaddInputs(self.inputRecorder.makeCoaddInputs())
790 coaddMaskedImage = coaddExposure.getMaskedImage()
791 subregionSizeArr = self.config.subregionSize
794 if self.config.doNImage:
795 nImage = afwImage.ImageU(skyInfo.bbox)
798 for subBBox
in self.
_subBBoxIter(skyInfo.bbox, subregionSize):
801 weightList, altMaskList, stats.flags, stats.ctrl,
803 except Exception
as e:
804 self.log.
fatal(
"Cannot compute coadd %s: %s", subBBox, e)
810 return pipeBase.Struct(coaddExposure=coaddExposure, nImage=nImage)
813 """Set the metadata for the coadd. 815 This basic implementation sets the filter from the first input. 819 coaddExposure : `lsst.afw.image.Exposure` 820 The target exposure for the coadd. 821 tempExpRefList : `list` 822 List of data references to tempExp. 826 assert len(tempExpRefList) == len(weightList),
"Length mismatch" 831 tempExpList = [tempExpRef.get(tempExpName +
"_sub",
834 for tempExpRef
in tempExpRefList]
835 numCcds = sum(len(tempExp.getInfo().getCoaddInputs().ccds)
for tempExp
in tempExpList)
837 coaddExposure.setFilter(tempExpList[0].getFilter())
838 coaddInputs = coaddExposure.getInfo().getCoaddInputs()
839 coaddInputs.ccds.reserve(numCcds)
840 coaddInputs.visits.reserve(len(tempExpList))
842 for tempExp, weight
in zip(tempExpList, weightList):
843 self.inputRecorder.addVisitToCoadd(coaddInputs, tempExp, weight)
845 if self.config.doUsePsfMatchedPolygons:
848 coaddInputs.visits.sort()
854 modelPsfList = [tempExp.getPsf()
for tempExp
in tempExpList]
855 modelPsfWidthList = [modelPsf.computeBBox().getWidth()
for modelPsf
in modelPsfList]
856 psf = modelPsfList[modelPsfWidthList.index(
max(modelPsfWidthList))]
858 psf = measAlg.CoaddPsf(coaddInputs.ccds, coaddExposure.getWcs(),
859 self.config.coaddPsf.makeControl())
860 coaddExposure.setPsf(psf)
861 apCorrMap = measAlg.makeCoaddApCorrMap(coaddInputs.ccds, coaddExposure.getBBox(afwImage.PARENT),
862 coaddExposure.getWcs())
863 coaddExposure.getInfo().setApCorrMap(apCorrMap)
864 if self.config.doAttachTransmissionCurve:
865 transmissionCurve = measAlg.makeCoaddTransmissionCurve(coaddExposure.getWcs(), coaddInputs.ccds)
866 coaddExposure.getInfo().setTransmissionCurve(transmissionCurve)
868 def assembleSubregion(self, coaddExposure, bbox, tempExpRefList, imageScalerList, weightList,
869 altMaskList, statsFlags, statsCtrl, nImage=None):
870 """Assemble the coadd for a sub-region. 872 For each coaddTempExp, check for (and swap in) an alternative mask 873 if one is passed. Remove mask planes listed in 874 `config.removeMaskPlanes`. Finally, stack the actual exposures using 875 `lsst.afw.math.statisticsStack` with the statistic specified by 876 statsFlags. Typically, the statsFlag will be one of lsst.afw.math.MEAN for 877 a mean-stack or `lsst.afw.math.MEANCLIP` for outlier rejection using 878 an N-sigma clipped mean where N and iterations are specified by 879 statsCtrl. Assign the stacked subregion back to the coadd. 883 coaddExposure : `lsst.afw.image.Exposure` 884 The target exposure for the coadd. 885 bbox : `lsst.afw.geom.Box` 887 tempExpRefList : `list` 888 List of data reference to tempExp. 889 imageScalerList : `list` 890 List of image scalers. 894 List of alternate masks to use rather than those stored with 895 tempExp, or None. Each element is dict with keys = mask plane 896 name to which to add the spans. 897 statsFlags : `lsst.afw.math.Property` 898 Property object for statistic for coadd. 899 statsCtrl : `lsst.afw.math.StatisticsControl` 900 Statistics control object for coadd. 901 nImage : `lsst.afw.image.ImageU`, optional 902 Keeps track of exposure count for each pixel. 904 self.log.
debug(
"Computing coadd over %s", bbox)
906 coaddExposure.mask.addMaskPlane(
"REJECTED")
907 coaddExposure.mask.addMaskPlane(
"CLIPPED")
908 coaddExposure.mask.addMaskPlane(
"SENSOR_EDGE")
910 clipped = afwImage.Mask.getPlaneBitMask(
"CLIPPED")
912 if nImage
is not None:
913 subNImage = afwImage.ImageU(bbox.getWidth(), bbox.getHeight())
914 for tempExpRef, imageScaler, altMask
in zip(tempExpRefList, imageScalerList, altMaskList):
915 exposure = tempExpRef.get(tempExpName +
"_sub", bbox=bbox)
916 maskedImage = exposure.getMaskedImage()
917 mask = maskedImage.getMask()
918 if altMask
is not None:
920 imageScaler.scaleMaskedImage(maskedImage)
924 if nImage
is not None:
925 subNImage.getArray()[maskedImage.getMask().getArray() & statsCtrl.getAndMask() == 0] += 1
926 if self.config.removeMaskPlanes:
928 maskedImageList.append(maskedImage)
930 with self.timer(
"stack"):
934 coaddExposure.maskedImage.assign(coaddSubregion, bbox)
935 if nImage
is not None:
936 nImage.assign(subNImage, bbox)
939 """Unset the mask of an image for mask planes specified in the config. 943 maskedImage : `lsst.afw.image.MaskedImage` 944 The masked image to be modified. 946 mask = maskedImage.getMask()
947 for maskPlane
in self.config.removeMaskPlanes:
949 mask &= ~mask.getPlaneBitMask(maskPlane)
951 self.log.
debug(
"Unable to remove mask plane %s: no mask plane with that name was found.",
956 """Map certain mask planes of the warps to new planes for the coadd. 958 If a pixel is rejected due to a mask value other than EDGE, NO_DATA, 959 or CLIPPED, set it to REJECTED on the coadd. 960 If a pixel is rejected due to EDGE, set the coadd pixel to SENSOR_EDGE. 961 If a pixel is rejected due to CLIPPED, set the coadd pixel to CLIPPED. 965 statsCtrl : `lsst.afw.math.StatisticsControl` 966 Statistics control object for coadd 970 maskMap : `list` of `tuple` of `int` 971 A list of mappings of mask planes of the warped exposures to 972 mask planes of the coadd. 974 edge = afwImage.Mask.getPlaneBitMask(
"EDGE")
975 noData = afwImage.Mask.getPlaneBitMask(
"NO_DATA")
976 clipped = afwImage.Mask.getPlaneBitMask(
"CLIPPED")
977 toReject = statsCtrl.getAndMask() & (~noData) & (~edge) & (~clipped)
978 maskMap = [(toReject, afwImage.Mask.getPlaneBitMask(
"REJECTED")),
979 (edge, afwImage.Mask.getPlaneBitMask(
"SENSOR_EDGE")),
984 """Apply in place alt mask formatted as SpanSets to a mask. 988 mask : `lsst.afw.image.Mask` 990 altMaskSpans : `dict` 991 SpanSet lists to apply. Each element contains the new mask 992 plane name (e.g. "CLIPPED and/or "NO_DATA") as the key, 993 and list of SpanSets to apply to the mask. 997 mask : `lsst.afw.image.Mask` 1000 if self.config.doUsePsfMatchedPolygons:
1001 if (
"NO_DATA" in altMaskSpans)
and (
"NO_DATA" in self.config.badMaskPlanes):
1006 for spanSet
in altMaskSpans[
'NO_DATA']:
1007 spanSet.clippedTo(mask.getBBox()).clearMask(mask, self.
getBadPixelMask())
1009 for plane, spanSetList
in altMaskSpans.items():
1010 maskClipValue = mask.addMaskPlane(plane)
1011 for spanSet
in spanSetList:
1012 spanSet.clippedTo(mask.getBBox()).setMask(mask, 2**maskClipValue)
1016 """Shrink coaddInputs' ccds' ValidPolygons in place. 1018 Either modify each ccd's validPolygon in place, or if CoaddInputs 1019 does not have a validPolygon, create one from its bbox. 1023 coaddInputs : `lsst.afw.image.coaddInputs` 1027 for ccd
in coaddInputs.ccds:
1028 polyOrig = ccd.getValidPolygon()
1029 validPolyBBox = polyOrig.getBBox()
if polyOrig
else ccd.getBBox()
1030 validPolyBBox.grow(-self.config.matchingKernelSize//2)
1032 validPolygon = polyOrig.intersectionSingle(validPolyBBox)
1034 validPolygon = afwGeom.polygon.Polygon(
afwGeom.Box2D(validPolyBBox))
1035 ccd.setValidPolygon(validPolygon)
1038 """Retrieve the bright object masks. 1040 Returns None on failure. 1044 dataRef : `lsst.daf.persistence.butlerSubset.ButlerDataRef` 1049 result : `lsst.daf.persistence.butlerSubset.ButlerDataRef` 1050 Bright object mask from the Butler object, or None if it cannot 1054 return dataRef.get(
"brightObjectMask", immediate=
True)
1055 except Exception
as e:
1056 self.log.
warn(
"Unable to read brightObjectMask for %s: %s", dataRef.dataId, e)
1060 """Set the bright object masks. 1064 exposure : `lsst.afw.image.Exposure` 1065 Exposure under consideration. 1066 dataId : `lsst.daf.persistence.dataId` 1067 Data identifier dict for patch. 1068 brightObjectMasks : `lsst.afw.table` 1069 Table of bright objects to mask. 1072 if brightObjectMasks
is None:
1073 self.log.
warn(
"Unable to apply bright object mask: none supplied")
1075 self.log.
info(
"Applying %d bright object masks to %s", len(brightObjectMasks), dataId)
1076 mask = exposure.getMaskedImage().getMask()
1077 wcs = exposure.getWcs()
1078 plateScale = wcs.getPixelScale().asArcseconds()
1080 for rec
in brightObjectMasks:
1082 if rec[
"type"] ==
"box":
1083 assert rec[
"angle"] == 0.0, (
"Angle != 0 for mask object %s" % rec[
"id"])
1084 width = rec[
"width"].asArcseconds()/plateScale
1085 height = rec[
"height"].asArcseconds()/plateScale
1093 elif rec[
"type"] ==
"circle":
1094 radius =
int(rec[
"radius"].asArcseconds()/plateScale)
1095 spans = afwGeom.SpanSet.fromShape(radius, offset=center)
1097 self.log.
warn(
"Unexpected region type %s at %s" % rec[
"type"], center)
1102 """Set INEXACT_PSF mask plane. 1104 If any of the input images isn't represented in the coadd (due to 1105 clipped pixels or chip gaps), the `CoaddPsf` will be inexact. Flag 1110 mask : `lsst.afw.image.Mask` 1111 Coadded exposure's mask, modified in-place. 1113 mask.addMaskPlane(
"INEXACT_PSF")
1114 inexactPsf = mask.getPlaneBitMask(
"INEXACT_PSF")
1115 sensorEdge = mask.getPlaneBitMask(
"SENSOR_EDGE")
1116 clipped = mask.getPlaneBitMask(
"CLIPPED")
1117 rejected = mask.getPlaneBitMask(
"REJECTED")
1118 array = mask.getArray()
1119 selected = array & (sensorEdge | clipped | rejected) > 0
1120 array[selected] |= inexactPsf
1123 def _makeArgumentParser(cls):
1124 """Create an argument parser. 1126 parser = pipeBase.ArgumentParser(name=cls.
_DefaultName)
1127 parser.add_id_argument(
"--id", cls.
ConfigClass().coaddName +
"Coadd_" +
1129 help=
"data ID, e.g. --id tract=12345 patch=1,2",
1130 ContainerClass=AssembleCoaddDataIdContainer)
1131 parser.add_id_argument(
"--selectId",
"calexp", help=
"data ID, e.g. --selectId visit=6789 ccd=0..9",
1132 ContainerClass=SelectDataIdContainer)
1136 def _subBBoxIter(bbox, subregionSize):
1137 """Iterate over subregions of a bbox. 1141 bbox : `lsst.afw.geom.Box2I` 1142 Bounding box over which to iterate. 1143 subregionSize: `lsst.afw.geom.Extent2I` 1148 subBBox : `lsst.afw.geom.Box2I` 1149 Next sub-bounding box of size ``subregionSize`` or smaller; each ``subBBox`` 1150 is contained within ``bbox``, so it may be smaller than ``subregionSize`` at 1151 the edges of ``bbox``, but it will never be empty. 1154 raise RuntimeError(
"bbox %s is empty" % (bbox,))
1155 if subregionSize[0] < 1
or subregionSize[1] < 1:
1156 raise RuntimeError(
"subregionSize %s must be nonzero" % (subregionSize,))
1158 for rowShift
in range(0, bbox.getHeight(), subregionSize[1]):
1159 for colShift
in range(0, bbox.getWidth(), subregionSize[0]):
1162 if subBBox.isEmpty():
1163 raise RuntimeError(
"Bug: empty bbox! bbox=%s, subregionSize=%s, " 1164 "colShift=%s, rowShift=%s" %
1165 (bbox, subregionSize, colShift, rowShift))
1170 """A version of `lsst.pipe.base.DataIdContainer` specialized for assembleCoadd. 1174 """Make self.refList from self.idList. 1179 Results of parsing command-line (with ``butler`` and ``log`` elements). 1181 datasetType = namespace.config.coaddName +
"Coadd" 1182 keysCoadd = namespace.butler.getKeys(datasetType=datasetType, level=self.level)
1184 for dataId
in self.idList:
1186 for key
in keysCoadd:
1187 if key
not in dataId:
1188 raise RuntimeError(
"--id must include " + key)
1190 dataRef = namespace.butler.dataRef(
1191 datasetType=datasetType,
1194 self.refList.
append(dataRef)
1198 """Function to count the number of pixels with a specific mask in a 1201 Find the intersection of mask & footprint. Count all pixels in the mask 1202 that are in the intersection that have bitmask set but do not have 1203 ignoreMask set. Return the count. 1207 mask : `lsst.afw.image.Mask` 1208 Mask to define intersection region by. 1209 footprint : `lsst.afw.detection.Footprint` 1210 Footprint to define the intersection region by. 1212 Specific mask that we wish to count the number of occurances of. 1214 Pixels to not consider. 1219 Count of number of pixels in footprint with specified mask. 1221 bbox = footprint.getBBox()
1222 bbox.clip(mask.getBBox(afwImage.PARENT))
1224 subMask = mask.Factory(mask, bbox, afwImage.PARENT)
1225 footprint.spans.setMask(fp, bitmask)
1226 return numpy.logical_and((subMask.getArray() & fp.getArray()) > 0,
1227 (subMask.getArray() & ignoreMask) == 0).sum()
1231 """Configuration parameters for the SafeClipAssembleCoaddTask. 1233 clipDetection = pexConfig.ConfigurableField(
1234 target=SourceDetectionTask,
1235 doc=
"Detect sources on difference between unclipped and clipped coadd")
1236 minClipFootOverlap = pexConfig.Field(
1237 doc=
"Minimum fractional overlap of clipped footprint with visit DETECTED to be clipped",
1241 minClipFootOverlapSingle = pexConfig.Field(
1242 doc=
"Minimum fractional overlap of clipped footprint with visit DETECTED to be " 1243 "clipped when only one visit overlaps",
1247 minClipFootOverlapDouble = pexConfig.Field(
1248 doc=
"Minimum fractional overlap of clipped footprints with visit DETECTED to be " 1249 "clipped when two visits overlap",
1253 maxClipFootOverlapDouble = pexConfig.Field(
1254 doc=
"Maximum fractional overlap of clipped footprints with visit DETECTED when " 1255 "considering two visits",
1259 minBigOverlap = pexConfig.Field(
1260 doc=
"Minimum number of pixels in footprint to use DETECTED mask from the single visits " 1261 "when labeling clipped footprints",
1267 """Set default values for clipDetection. 1271 The numeric values for these configuration parameters were 1272 empirically determined, future work may further refine them. 1274 AssembleCoaddConfig.setDefaults(self)
1290 log.warn(
"Additional Sigma-clipping not allowed in Safe-clipped Coadds. " 1291 "Ignoring doSigmaClip.")
1294 raise ValueError(
"Only MEAN statistic allowed for final stacking in SafeClipAssembleCoadd " 1295 "(%s chosen). Please set statistic to MEAN." 1297 AssembleCoaddTask.ConfigClass.validate(self)
1301 """Assemble a coadded image from a set of coadded temporary exposures, 1302 being careful to clip & flag areas with potential artifacts. 1304 In ``AssembleCoaddTask``, we compute the coadd as an clipped mean (i.e., 1305 we clip outliers). The problem with doing this is that when computing the 1306 coadd PSF at a given location, individual visit PSFs from visits with 1307 outlier pixels contribute to the coadd PSF and cannot be treated correctly. 1308 In this task, we correct for this behavior by creating a new 1309 ``badMaskPlane`` 'CLIPPED'. We populate this plane on the input 1310 coaddTempExps and the final coadd where 1312 i. difference imaging suggests that there is an outlier and 1313 ii. this outlier appears on only one or two images. 1315 Such regions will not contribute to the final coadd. Furthermore, any 1316 routine to determine the coadd PSF can now be cognizant of clipped regions. 1317 Note that the algorithm implemented by this task is preliminary and works 1318 correctly for HSC data. Parameter modifications and or considerable 1319 redesigning of the algorithm is likley required for other surveys. 1321 ``SafeClipAssembleCoaddTask`` uses a ``SourceDetectionTask`` 1322 "clipDetection" subtask and also sub-classes ``AssembleCoaddTask``. 1323 You can retarget the ``SourceDetectionTask`` "clipDetection" subtask 1328 The `lsst.pipe.base.cmdLineTask.CmdLineTask` interface supports a 1329 flag ``-d`` to import ``debug.py`` from your ``PYTHONPATH``; 1330 see `baseDebug` for more about ``debug.py`` files. 1331 `SafeClipAssembleCoaddTask` has no debug variables of its own. 1332 The ``SourceDetectionTask`` "clipDetection" subtasks may support debug 1333 variables. See the documetation for `SourceDetectionTask` "clipDetection" 1334 for further information. 1338 `SafeClipAssembleCoaddTask` assembles a set of warped ``coaddTempExp`` 1339 images into a coadded image. The `SafeClipAssembleCoaddTask` is invoked by 1340 running assembleCoadd.py *without* the flag '--legacyCoadd'. 1342 Usage of ``assembleCoadd.py`` expects a data reference to the tract patch 1343 and filter to be coadded (specified using 1344 '--id = [KEY=VALUE1[^VALUE2[^VALUE3...] [KEY=VALUE1[^VALUE2[^VALUE3...] ...]]') 1345 along with a list of coaddTempExps to attempt to coadd (specified using 1346 '--selectId [KEY=VALUE1[^VALUE2[^VALUE3...] [KEY=VALUE1[^VALUE2[^VALUE3...] ...]]'). 1347 Only the coaddTempExps that cover the specified tract and patch will be 1348 coadded. A list of the available optional arguments can be obtained by 1349 calling assembleCoadd.py with the --help command line argument: 1351 .. code-block:: none 1353 assembleCoadd.py --help 1355 To demonstrate usage of the `SafeClipAssembleCoaddTask` in the larger 1356 context of multi-band processing, we will generate the HSC-I & -R band 1357 coadds from HSC engineering test data provided in the ci_hsc package. 1358 To begin, assuming that the lsst stack has been already set up, we must 1359 set up the obs_subaru and ci_hsc packages. This defines the environment 1360 variable $CI_HSC_DIR and points at the location of the package. The raw 1361 HSC data live in the ``$CI_HSC_DIR/raw`` directory. To begin assembling 1362 the coadds, we must first 1365 process the individual ccds in $CI_HSC_RAW to produce calibrated exposures 1367 create a skymap that covers the area of the sky present in the raw exposures 1368 - ``makeCoaddTempExp`` 1369 warp the individual calibrated exposures to the tangent plane of the coadd</DD> 1371 We can perform all of these steps by running 1373 .. code-block:: none 1375 $CI_HSC_DIR scons warp-903986 warp-904014 warp-903990 warp-904010 warp-903988 1377 This will produce warped coaddTempExps for each visit. To coadd the 1378 warped data, we call ``assembleCoadd.py`` as follows: 1380 .. code-block:: none 1382 assembleCoadd.py $CI_HSC_DIR/DATA --id patch=5,4 tract=0 filter=HSC-I \ 1383 --selectId visit=903986 ccd=16 --selectId visit=903986 ccd=22 --selectId visit=903986 ccd=23 \ 1384 --selectId visit=903986 ccd=100--selectId visit=904014 ccd=1 --selectId visit=904014 ccd=6 \ 1385 --selectId visit=904014 ccd=12 --selectId visit=903990 ccd=18 --selectId visit=903990 ccd=25 \ 1386 --selectId visit=904010 ccd=4 --selectId visit=904010 ccd=10 --selectId visit=904010 ccd=100 \ 1387 --selectId visit=903988 ccd=16 --selectId visit=903988 ccd=17 --selectId visit=903988 ccd=23 \ 1388 --selectId visit=903988 ccd=24 1390 This will process the HSC-I band data. The results are written in 1391 ``$CI_HSC_DIR/DATA/deepCoadd-results/HSC-I``. 1393 You may also choose to run: 1395 .. code-block:: none 1397 scons warp-903334 warp-903336 warp-903338 warp-903342 warp-903344 warp-903346 nnn 1398 assembleCoadd.py $CI_HSC_DIR/DATA --id patch=5,4 tract=0 filter=HSC-R --selectId visit=903334 ccd=16 \ 1399 --selectId visit=903334 ccd=22 --selectId visit=903334 ccd=23 --selectId visit=903334 ccd=100 \ 1400 --selectId visit=903336 ccd=17 --selectId visit=903336 ccd=24 --selectId visit=903338 ccd=18 \ 1401 --selectId visit=903338 ccd=25 --selectId visit=903342 ccd=4 --selectId visit=903342 ccd=10 \ 1402 --selectId visit=903342 ccd=100 --selectId visit=903344 ccd=0 --selectId visit=903344 ccd=5 \ 1403 --selectId visit=903344 ccd=11 --selectId visit=903346 ccd=1 --selectId visit=903346 ccd=6 \ 1404 --selectId visit=903346 ccd=12 1406 to generate the coadd for the HSC-R band if you are interested in following 1407 multiBand Coadd processing as discussed in ``pipeTasks_multiBand``. 1409 ConfigClass = SafeClipAssembleCoaddConfig
1410 _DefaultName =
"safeClipAssembleCoadd" 1413 AssembleCoaddTask.__init__(self, *args, **kwargs)
1414 schema = afwTable.SourceTable.makeMinimalSchema()
1415 self.makeSubtask(
"clipDetection", schema=schema)
1417 def run(self, skyInfo, tempExpRefList, imageScalerList, weightList, *args, **kwargs):
1418 """Assemble the coadd for a region. 1420 Compute the difference of coadds created with and without outlier 1421 rejection to identify coadd pixels that have outlier values in some 1423 Detect clipped regions on the difference image and mark these regions 1424 on the one or two individual coaddTempExps where they occur if there 1425 is significant overlap between the clipped region and a source. This 1426 leaves us with a set of footprints from the difference image that have 1427 been identified as having occured on just one or two individual visits. 1428 However, these footprints were generated from a difference image. It 1429 is conceivable for a large diffuse source to have become broken up 1430 into multiple footprints acrosss the coadd difference in this process. 1431 Determine the clipped region from all overlapping footprints from the 1432 detected sources in each visit - these are big footprints. 1433 Combine the small and big clipped footprints and mark them on a new 1435 Generate the coadd using `AssembleCoaddTask.run` without outlier 1436 removal. Clipped footprints will no longer make it into the coadd 1437 because they are marked in the new bad mask plane. 1441 skyInfo : `lsst.pipe.base.Struct` 1442 Patch geometry information, from getSkyInfo 1443 tempExpRefList : `list` 1444 List of data reference to tempExp 1445 imageScalerList : `list` 1446 List of image scalers 1452 result : `lsst.pipe.base.Struct` 1453 Result struct with components: 1455 - ``coaddExposure``: coadded exposure (``lsst.afw.image.Exposure``). 1456 - ``nImage``: exposure count image (``lsst.afw.image.Image``). 1460 args and kwargs are passed but ignored in order to match the call 1461 signature expected by the parent task. 1464 mask = exp.getMaskedImage().getMask()
1465 mask.addMaskPlane(
"CLIPPED")
1467 result = self.
detectClip(exp, tempExpRefList)
1469 self.log.
info(
'Found %d clipped objects', len(result.clipFootprints))
1471 maskClipValue = mask.getPlaneBitMask(
"CLIPPED")
1472 maskDetValue = mask.getPlaneBitMask(
"DETECTED") | mask.getPlaneBitMask(
"DETECTED_NEGATIVE")
1474 bigFootprints = self.
detectClipBig(result.clipSpans, result.clipFootprints, result.clipIndices,
1475 result.detectionFootprints, maskClipValue, maskDetValue,
1478 maskClip = mask.Factory(mask.getBBox(afwImage.PARENT))
1479 afwDet.setMaskFromFootprintList(maskClip, result.clipFootprints, maskClipValue)
1481 maskClipBig = maskClip.Factory(mask.getBBox(afwImage.PARENT))
1482 afwDet.setMaskFromFootprintList(maskClipBig, bigFootprints, maskClipValue)
1483 maskClip |= maskClipBig
1486 badMaskPlanes = self.config.badMaskPlanes[:]
1487 badMaskPlanes.append(
"CLIPPED")
1488 badPixelMask = afwImage.Mask.getPlaneBitMask(badMaskPlanes)
1489 return AssembleCoaddTask.run(self, skyInfo, tempExpRefList, imageScalerList, weightList,
1490 result.clipSpans, mask=badPixelMask)
1493 """Return an exposure that contains the difference between unclipped 1496 Generate a difference image between clipped and unclipped coadds. 1497 Compute the difference image by subtracting an outlier-clipped coadd 1498 from an outlier-unclipped coadd. Return the difference image. 1502 skyInfo : `lsst.pipe.base.Struct` 1503 Patch geometry information, from getSkyInfo 1504 tempExpRefList : `list` 1505 List of data reference to tempExp 1506 imageScalerList : `list` 1507 List of image scalers 1513 exp : `lsst.afw.image.Exposure` 1514 Difference image of unclipped and clipped coadd wrapped in an Exposure 1519 configIntersection = {k: getattr(self.config, k)
1520 for k, v
in self.config.toDict().
items()
if (k
in config.keys())}
1521 config.update(**configIntersection)
1524 config.statistic =
'MEAN' 1526 coaddMean = task.run(skyInfo, tempExpRefList, imageScalerList, weightList).coaddExposure
1528 config.statistic =
'MEANCLIP' 1530 coaddClip = task.run(skyInfo, tempExpRefList, imageScalerList, weightList).coaddExposure
1532 coaddDiff = coaddMean.getMaskedImage().
Factory(coaddMean.getMaskedImage())
1533 coaddDiff -= coaddClip.getMaskedImage()
1534 exp = afwImage.ExposureF(coaddDiff)
1535 exp.setPsf(coaddMean.getPsf())
1539 """Detect clipped regions on an exposure and set the mask on the 1540 individual tempExp masks. 1542 Detect footprints in the difference image after smoothing the 1543 difference image with a Gaussian kernal. Identify footprints that 1544 overlap with one or two input ``coaddTempExps`` by comparing the 1545 computed overlap fraction to thresholds set in the config. A different 1546 threshold is applied depending on the number of overlapping visits 1547 (restricted to one or two). If the overlap exceeds the thresholds, 1548 the footprint is considered "CLIPPED" and is marked as such on the 1549 coaddTempExp. Return a struct with the clipped footprints, the indices 1550 of the ``coaddTempExps`` that end up overlapping with the clipped 1551 footprints, and a list of new masks for the ``coaddTempExps``. 1555 exp : `lsst.afw.image.Exposure` 1556 Exposure to run detection on. 1557 tempExpRefList : `list` 1558 List of data reference to tempExp. 1562 result : `lsst.pipe.base.Struct` 1563 Result struct with components: 1565 - ``clipFootprints``: list of clipped footprints. 1566 - ``clipIndices``: indices for each ``clippedFootprint`` in 1568 - ``clipSpans``: List of dictionaries containing spanSet lists 1569 to clip. Each element contains the new maskplane name 1570 ("CLIPPED") as the key and list of ``SpanSets`` as the value. 1571 - ``detectionFootprints``: List of DETECTED/DETECTED_NEGATIVE plane 1572 compressed into footprints. 1574 mask = exp.getMaskedImage().getMask()
1575 maskDetValue = mask.getPlaneBitMask(
"DETECTED") | mask.getPlaneBitMask(
"DETECTED_NEGATIVE")
1576 fpSet = self.clipDetection.detectFootprints(exp, doSmooth=
True, clearMask=
True)
1578 fpSet.positive.merge(fpSet.negative)
1579 footprints = fpSet.positive
1580 self.log.
info(
'Found %d potential clipped objects', len(footprints.getFootprints()))
1585 artifactSpanSets = [{
'CLIPPED':
list()}
for _
in tempExpRefList]
1588 visitDetectionFootprints = []
1590 dims = [len(tempExpRefList), len(footprints.getFootprints())]
1591 overlapDetArr = numpy.zeros(dims, dtype=numpy.uint16)
1592 ignoreArr = numpy.zeros(dims, dtype=numpy.uint16)
1595 for i, warpRef
in enumerate(tempExpRefList):
1597 immediate=
True).getMaskedImage().getMask()
1598 maskVisitDet = tmpExpMask.Factory(tmpExpMask, tmpExpMask.getBBox(afwImage.PARENT),
1599 afwImage.PARENT,
True)
1600 maskVisitDet &= maskDetValue
1602 visitDetectionFootprints.append(visitFootprints)
1604 for j, footprint
in enumerate(footprints.getFootprints()):
1609 for j, footprint
in enumerate(footprints.getFootprints()):
1610 nPixel = footprint.getArea()
1613 for i
in range(len(tempExpRefList)):
1614 ignore = ignoreArr[i, j]
1615 overlapDet = overlapDetArr[i, j]
1616 totPixel = nPixel - ignore
1619 if ignore > overlapDet
or totPixel <= 0.5*nPixel
or overlapDet == 0:
1621 overlap.append(overlapDet/
float(totPixel))
1624 overlap = numpy.array(overlap)
1625 if not len(overlap):
1632 if len(overlap) == 1:
1633 if overlap[0] > self.config.minClipFootOverlapSingle:
1638 clipIndex = numpy.where(overlap > self.config.minClipFootOverlap)[0]
1639 if len(clipIndex) == 1:
1641 keepIndex = [clipIndex[0]]
1644 clipIndex = numpy.where(overlap > self.config.minClipFootOverlapDouble)[0]
1645 if len(clipIndex) == 2
and len(overlap) > 3:
1646 clipIndexComp = numpy.where(overlap <= self.config.minClipFootOverlapDouble)[0]
1647 if numpy.max(overlap[clipIndexComp]) <= self.config.maxClipFootOverlapDouble:
1649 keepIndex = clipIndex
1654 for index
in keepIndex:
1655 globalIndex = indexList[index]
1656 artifactSpanSets[globalIndex][
'CLIPPED'].
append(footprint.spans)
1658 clipIndices.append(numpy.array(indexList)[keepIndex])
1659 clipFootprints.append(footprint)
1661 return pipeBase.Struct(clipFootprints=clipFootprints, clipIndices=clipIndices,
1662 clipSpans=artifactSpanSets, detectionFootprints=visitDetectionFootprints)
1664 def detectClipBig(self, clipList, clipFootprints, clipIndices, detectionFootprints,
1665 maskClipValue, maskDetValue, coaddBBox):
1666 """Return individual warp footprints for large artifacts and append 1667 them to ``clipList`` in place. 1669 Identify big footprints composed of many sources in the coadd 1670 difference that may have originated in a large diffuse source in the 1671 coadd. We do this by indentifying all clipped footprints that overlap 1672 significantly with each source in all the coaddTempExps. 1677 List of alt mask SpanSets with clipping information. Modified. 1678 clipFootprints : `list` 1679 List of clipped footprints. 1680 clipIndices : `list` 1681 List of which entries in tempExpClipList each footprint belongs to. 1683 Mask value of clipped pixels. 1685 Mask value of detected pixels. 1686 coaddBBox : `lsst.afw.geom.Box` 1687 BBox of the coadd and warps. 1691 bigFootprintsCoadd : `list` 1692 List of big footprints 1694 bigFootprintsCoadd = []
1696 for index, (clippedSpans, visitFootprints)
in enumerate(zip(clipList, detectionFootprints)):
1697 maskVisitDet = afwImage.MaskX(coaddBBox, 0x0)
1698 for footprint
in visitFootprints.getFootprints():
1699 footprint.spans.setMask(maskVisitDet, maskDetValue)
1702 clippedFootprintsVisit = []
1703 for foot, clipIndex
in zip(clipFootprints, clipIndices):
1704 if index
not in clipIndex:
1706 clippedFootprintsVisit.append(foot)
1707 maskVisitClip = maskVisitDet.Factory(maskVisitDet.getBBox(afwImage.PARENT))
1708 afwDet.setMaskFromFootprintList(maskVisitClip, clippedFootprintsVisit, maskClipValue)
1710 bigFootprintsVisit = []
1711 for foot
in visitFootprints.getFootprints():
1712 if foot.getArea() < self.config.minBigOverlap:
1715 if nCount > self.config.minBigOverlap:
1716 bigFootprintsVisit.append(foot)
1717 bigFootprintsCoadd.append(foot)
1719 for footprint
in bigFootprintsVisit:
1720 clippedSpans[
"CLIPPED"].
append(footprint.spans)
1722 return bigFootprintsCoadd
1726 assembleStaticSkyModel = pexConfig.ConfigurableField(
1727 target=AssembleCoaddTask,
1728 doc=
"Task to assemble an artifact-free, PSF-matched Coadd to serve as a" 1729 " naive/first-iteration model of the static sky.",
1731 detect = pexConfig.ConfigurableField(
1732 target=SourceDetectionTask,
1733 doc=
"Detect outlier sources on difference between each psfMatched warp and static sky model" 1735 detectTemplate = pexConfig.ConfigurableField(
1736 target=SourceDetectionTask,
1737 doc=
"Detect sources on static sky model. Only used if doPreserveContainedBySource is True" 1739 maxNumEpochs = pexConfig.Field(
1740 doc=
"Charactistic maximum local number of epochs/visits in which an artifact candidate can appear " 1741 "and still be masked. The effective maxNumEpochs is a broken linear function of local " 1742 "number of epochs (N): min(maxFractionEpochsLow*N, maxNumEpochs + maxFractionEpochsHigh*N). " 1743 "For each footprint detected on the image difference between the psfMatched warp and static sky " 1744 "model, if a significant fraction of pixels (defined by spatialThreshold) are residuals in more " 1745 "than the computed effective maxNumEpochs, the artifact candidate is deemed persistant rather " 1746 "than transient and not masked.",
1750 maxFractionEpochsLow = pexConfig.RangeField(
1751 doc=
"Fraction of local number of epochs (N) to use as effective maxNumEpochs for low N. " 1752 "Effective maxNumEpochs = " 1753 "min(maxFractionEpochsLow * N, maxNumEpochs + maxFractionEpochsHigh * N)",
1758 maxFractionEpochsHigh = pexConfig.RangeField(
1759 doc=
"Fraction of local number of epochs (N) to use as effective maxNumEpochs for high N. " 1760 "Effective maxNumEpochs = " 1761 "min(maxFractionEpochsLow * N, maxNumEpochs + maxFractionEpochsHigh * N)",
1766 spatialThreshold = pexConfig.RangeField(
1767 doc=
"Unitless fraction of pixels defining how much of the outlier region has to meet the " 1768 "temporal criteria. If 0, clip all. If 1, clip none.",
1772 inclusiveMin=
True, inclusiveMax=
True 1774 doScaleWarpVariance = pexConfig.Field(
1775 doc=
"Rescale Warp variance plane using empirical noise?",
1779 scaleWarpVariance = pexConfig.ConfigurableField(
1780 target=ScaleVarianceTask,
1781 doc=
"Rescale variance on warps",
1783 doPreserveContainedBySource = pexConfig.Field(
1784 doc=
"Rescue artifacts from clipping that completely lie within a footprint detected" 1785 "on the PsfMatched Template Coadd. Replicates a behavior of SafeClip.",
1789 doPrefilterArtifacts = pexConfig.Field(
1790 doc=
"Ignore artifact candidates that are mostly covered by the bad pixel mask, " 1791 "because they will be excluded anyway. This prevents them from contributing " 1792 "to the outlier epoch count image and potentially being labeled as persistant." 1793 "'Mostly' is defined by the config 'prefilterArtifactsRatio'.",
1797 prefilterArtifactsMaskPlanes = pexConfig.ListField(
1798 doc=
"Prefilter artifact candidates that are mostly covered by these bad mask planes.",
1800 default=(
'NO_DATA',
'BAD',
'SAT',
'SUSPECT'),
1802 prefilterArtifactsRatio = pexConfig.Field(
1803 doc=
"Prefilter artifact candidates with less than this fraction overlapping good pixels",
1807 psfMatchedWarps = pipeBase.InputDatasetField(
1808 doc=(
"PSF-Matched Warps are required by CompareWarp regardless of the coadd type requested. " 1809 "Only PSF-Matched Warps make sense for image subtraction. " 1810 "Therefore, they must be in the InputDatasetField and made available to the task."),
1811 nameTemplate=
"{inputCoaddName}Coadd_psfMatchedWarp",
1812 storageClass=
"ExposureF",
1813 dimensions=(
"tract",
"patch",
"skymap",
"visit"),
1818 AssembleCoaddConfig.setDefaults(self)
1834 self.
detect.doTempLocalBackground =
False 1835 self.
detect.reEstimateBackground =
False 1836 self.
detect.returnOriginalFootprints =
False 1837 self.
detect.thresholdPolarity =
"both" 1838 self.
detect.thresholdValue = 5
1839 self.
detect.nSigmaToGrow = 2
1840 self.
detect.minPixels = 4
1841 self.
detect.isotropicGrow =
True 1842 self.
detect.thresholdType =
"pixel_stdev" 1850 """Assemble a compareWarp coadded image from a set of warps 1851 by masking artifacts detected by comparing PSF-matched warps. 1853 In ``AssembleCoaddTask``, we compute the coadd as an clipped mean (i.e., 1854 we clip outliers). The problem with doing this is that when computing the 1855 coadd PSF at a given location, individual visit PSFs from visits with 1856 outlier pixels contribute to the coadd PSF and cannot be treated correctly. 1857 In this task, we correct for this behavior by creating a new badMaskPlane 1858 'CLIPPED' which marks pixels in the individual warps suspected to contain 1859 an artifact. We populate this plane on the input warps by comparing 1860 PSF-matched warps with a PSF-matched median coadd which serves as a 1861 model of the static sky. Any group of pixels that deviates from the 1862 PSF-matched template coadd by more than config.detect.threshold sigma, 1863 is an artifact candidate. The candidates are then filtered to remove 1864 variable sources and sources that are difficult to subtract such as 1865 bright stars. This filter is configured using the config parameters 1866 ``temporalThreshold`` and ``spatialThreshold``. The temporalThreshold is 1867 the maximum fraction of epochs that the deviation can appear in and still 1868 be considered an artifact. The spatialThreshold is the maximum fraction of 1869 pixels in the footprint of the deviation that appear in other epochs 1870 (where other epochs is defined by the temporalThreshold). If the deviant 1871 region meets this criteria of having a significant percentage of pixels 1872 that deviate in only a few epochs, these pixels have the 'CLIPPED' bit 1873 set in the mask. These regions will not contribute to the final coadd. 1874 Furthermore, any routine to determine the coadd PSF can now be cognizant 1875 of clipped regions. Note that the algorithm implemented by this task is 1876 preliminary and works correctly for HSC data. Parameter modifications and 1877 or considerable redesigning of the algorithm is likley required for other 1880 ``CompareWarpAssembleCoaddTask`` sub-classes 1881 ``AssembleCoaddTask`` and instantiates ``AssembleCoaddTask`` 1882 as a subtask to generate the TemplateCoadd (the model of the static sky). 1886 The `lsst.pipe.base.cmdLineTask.CmdLineTask` interface supports a 1887 flag ``-d`` to import ``debug.py`` from your ``PYTHONPATH``; see 1888 ``baseDebug`` for more about ``debug.py`` files. 1890 This task supports the following debug variables: 1893 If True then save the Epoch Count Image as a fits file in the `figPath` 1895 Path to save the debug fits images and figures 1897 For example, put something like: 1899 .. code-block:: python 1902 def DebugInfo(name): 1903 di = lsstDebug.getInfo(name) 1904 if name == "lsst.pipe.tasks.assembleCoadd": 1905 di.saveCountIm = True 1906 di.figPath = "/desired/path/to/debugging/output/images" 1908 lsstDebug.Info = DebugInfo 1910 into your ``debug.py`` file and run ``assemebleCoadd.py`` with the 1911 ``--debug`` flag. Some subtasks may have their own debug variables; 1912 see individual Task documentation. 1916 ``CompareWarpAssembleCoaddTask`` assembles a set of warped images into a 1917 coadded image. The ``CompareWarpAssembleCoaddTask`` is invoked by running 1918 ``assembleCoadd.py`` with the flag ``--compareWarpCoadd``. 1919 Usage of ``assembleCoadd.py`` expects a data reference to the tract patch 1920 and filter to be coadded (specified using 1921 '--id = [KEY=VALUE1[^VALUE2[^VALUE3...] [KEY=VALUE1[^VALUE2[^VALUE3...] ...]]') 1922 along with a list of coaddTempExps to attempt to coadd (specified using 1923 '--selectId [KEY=VALUE1[^VALUE2[^VALUE3...] [KEY=VALUE1[^VALUE2[^VALUE3...] ...]]'). 1924 Only the warps that cover the specified tract and patch will be coadded. 1925 A list of the available optional arguments can be obtained by calling 1926 ``assembleCoadd.py`` with the ``--help`` command line argument: 1928 .. code-block:: none 1930 assembleCoadd.py --help 1932 To demonstrate usage of the ``CompareWarpAssembleCoaddTask`` in the larger 1933 context of multi-band processing, we will generate the HSC-I & -R band 1934 oadds from HSC engineering test data provided in the ``ci_hsc`` package. 1935 To begin, assuming that the lsst stack has been already set up, we must 1936 set up the ``obs_subaru`` and ``ci_hsc`` packages. 1937 This defines the environment variable ``$CI_HSC_DIR`` and points at the 1938 location of the package. The raw HSC data live in the ``$CI_HSC_DIR/raw`` 1939 directory. To begin assembling the coadds, we must first 1942 process the individual ccds in $CI_HSC_RAW to produce calibrated exposures 1944 create a skymap that covers the area of the sky present in the raw exposures 1946 warp the individual calibrated exposures to the tangent plane of the coadd 1948 We can perform all of these steps by running 1950 .. code-block:: none 1952 $CI_HSC_DIR scons warp-903986 warp-904014 warp-903990 warp-904010 warp-903988 1954 This will produce warped ``coaddTempExps`` for each visit. To coadd the 1955 warped data, we call ``assembleCoadd.py`` as follows: 1957 .. code-block:: none 1959 assembleCoadd.py --compareWarpCoadd $CI_HSC_DIR/DATA --id patch=5,4 tract=0 filter=HSC-I \ 1960 --selectId visit=903986 ccd=16 --selectId visit=903986 ccd=22 --selectId visit=903986 ccd=23 \ 1961 --selectId visit=903986 ccd=100 --selectId visit=904014 ccd=1 --selectId visit=904014 ccd=6 \ 1962 --selectId visit=904014 ccd=12 --selectId visit=903990 ccd=18 --selectId visit=903990 ccd=25 \ 1963 --selectId visit=904010 ccd=4 --selectId visit=904010 ccd=10 --selectId visit=904010 ccd=100 \ 1964 --selectId visit=903988 ccd=16 --selectId visit=903988 ccd=17 --selectId visit=903988 ccd=23 \ 1965 --selectId visit=903988 ccd=24 1967 This will process the HSC-I band data. The results are written in 1968 ``$CI_HSC_DIR/DATA/deepCoadd-results/HSC-I``. 1970 ConfigClass = CompareWarpAssembleCoaddConfig
1971 _DefaultName =
"compareWarpAssembleCoadd" 1974 AssembleCoaddTask.__init__(self, *args, **kwargs)
1975 self.makeSubtask(
"assembleStaticSkyModel")
1976 detectionSchema = afwTable.SourceTable.makeMinimalSchema()
1977 self.makeSubtask(
"detect", schema=detectionSchema)
1978 if self.config.doPreserveContainedBySource:
1979 self.makeSubtask(
"detectTemplate", schema=afwTable.SourceTable.makeMinimalSchema())
1980 if self.config.doScaleWarpVariance:
1981 self.makeSubtask(
"scaleWarpVariance")
1984 """Make inputs specific to Subclass with Gen 3 API 1986 Calls Gen3 `adaptArgsAndRun` instead of the Gen2 specific `runDataRef` 1988 Duplicates interface of`adaptArgsAndRun` method. 1989 Available to be implemented by subclasses only if they need the 1990 coadd dataRef for performing preliminary processing before 1991 assembling the coadd. 1996 Keys are the names of the configs describing input dataset types. 1997 Values are input Python-domain data objects (or lists of objects) 1998 retrieved from data butler. 1999 inputDataIds : `dict` 2000 Keys are the names of the configs describing input dataset types. 2001 Values are DataIds (or lists of DataIds) that task consumes for 2002 corresponding dataset type. 2003 DataIds are guaranteed to match data objects in ``inputData``. 2004 outputDataIds : `dict` 2005 Keys are the names of the configs describing input dataset types. 2006 Values are DataIds (or lists of DataIds) that task is to produce 2007 for corresponding dataset type. 2008 butler : `lsst.daf.butler.Butler` 2009 Gen3 Butler object for fetching additional data products before 2014 result : `lsst.pipe.base.Struct` 2015 Result struct with components: 2017 - ``templateCoadd`` : coadded exposure (``lsst.afw.image.Exposure``) 2018 - ``nImage``: N Image (``lsst.afw.image.Image``) 2022 templateCoadd = self.assembleStaticSkyModel.
adaptArgsAndRun(inputData, inputDataIds,
2023 outputDataIds, butler)
2024 if templateCoadd
is None:
2027 return pipeBase.Struct(templateCoadd=templateCoadd.coaddExposure,
2028 nImage=templateCoadd.nImage)
2031 """Make inputs specific to Subclass. 2033 Generate a templateCoadd to use as a native model of static sky to 2034 subtract from warps. 2038 dataRef : `lsst.daf.persistence.butlerSubset.ButlerDataRef` 2039 Butler dataRef for supplementary data. 2040 selectDataList : `list` (optional) 2041 Optional List of data references to Calexps. 2042 warpRefList : `list` (optional) 2043 Optional List of data references to Warps. 2047 result : `lsst.pipe.base.Struct` 2048 Result struct with components: 2050 - ``templateCoadd``: coadded exposure (``lsst.afw.image.Exposure``) 2051 - ``nImage``: N Image (``lsst.afw.image.Image``) 2053 templateCoadd = self.assembleStaticSkyModel.
runDataRef(dataRef, selectDataList, warpRefList)
2054 if templateCoadd
is None:
2057 return pipeBase.Struct(templateCoadd=templateCoadd.coaddExposure,
2058 nImage=templateCoadd.nImage)
2060 def _noTemplateMessage(self, warpType):
2061 warpName = (warpType[0].upper() + warpType[1:])
2062 message =
"""No %(warpName)s warps were found to build the template coadd which is 2063 required to run CompareWarpAssembleCoaddTask. To continue assembling this type of coadd, 2064 first either rerun makeCoaddTempExp with config.make%(warpName)s=True or 2065 coaddDriver with config.makeCoadTempExp.make%(warpName)s=True, before assembleCoadd. 2067 Alternatively, to use another algorithm with existing warps, retarget the CoaddDriverConfig to 2068 another algorithm like: 2070 from lsst.pipe.tasks.assembleCoadd import SafeClipAssembleCoaddTask 2071 config.assemble.retarget(SafeClipAssembleCoaddTask) 2072 """ % {
"warpName": warpName}
2075 def run(self, skyInfo, tempExpRefList, imageScalerList, weightList,
2076 supplementaryData, *args, **kwargs):
2077 """Assemble the coadd. 2079 Find artifacts and apply them to the warps' masks creating a list of 2080 alternative masks with a new "CLIPPED" plane and updated "NO_DATA" 2081 plane. Then pass these alternative masks to the base class's `run` 2086 skyInfo : `lsst.pipe.base.Struct` 2087 Patch geometry information. 2088 tempExpRefList : `list` 2089 List of data references to warps. 2090 imageScalerList : `list` 2091 List of image scalers. 2094 supplementaryData : `lsst.pipe.base.Struct` 2095 This Struct must contain a ``templateCoadd`` that serves as the 2096 model of the static sky. 2100 result : `lsst.pipe.base.Struct` 2101 Result struct with components: 2103 - ``coaddExposure``: coadded exposure (``lsst.afw.image.Exposure``). 2104 - ``nImage``: exposure count image (``lsst.afw.image.Image``), if requested. 2106 templateCoadd = supplementaryData.templateCoadd
2107 spanSetMaskList = self.
findArtifacts(templateCoadd, tempExpRefList, imageScalerList)
2108 badMaskPlanes = self.config.badMaskPlanes[:]
2109 badMaskPlanes.append(
"CLIPPED")
2110 badPixelMask = afwImage.Mask.getPlaneBitMask(badMaskPlanes)
2112 result = AssembleCoaddTask.run(self, skyInfo, tempExpRefList, imageScalerList, weightList,
2113 spanSetMaskList, mask=badPixelMask)
2117 self.
applyAltEdgeMask(result.coaddExposure.maskedImage.mask, spanSetMaskList)
2121 """Propagate alt EDGE mask to SENSOR_EDGE AND INEXACT_PSF planes. 2125 mask : `lsst.afw.image.Mask` 2127 altMaskList : `list` 2128 List of Dicts containing ``spanSet`` lists. 2129 Each element contains the new mask plane name (e.g. "CLIPPED 2130 and/or "NO_DATA") as the key, and list of ``SpanSets`` to apply to 2133 maskValue = mask.getPlaneBitMask([
"SENSOR_EDGE",
"INEXACT_PSF"])
2134 for visitMask
in altMaskList:
2135 if "EDGE" in visitMask:
2136 for spanSet
in visitMask[
'EDGE']:
2137 spanSet.clippedTo(mask.getBBox()).setMask(mask, maskValue)
2142 Loop through warps twice. The first loop builds a map with the count 2143 of how many epochs each pixel deviates from the templateCoadd by more 2144 than ``config.chiThreshold`` sigma. The second loop takes each 2145 difference image and filters the artifacts detected in each using 2146 count map to filter out variable sources and sources that are 2147 difficult to subtract cleanly. 2151 templateCoadd : `lsst.afw.image.Exposure` 2152 Exposure to serve as model of static sky. 2153 tempExpRefList : `list` 2154 List of data references to warps. 2155 imageScalerList : `list` 2156 List of image scalers. 2161 List of dicts containing information about CLIPPED 2162 (i.e., artifacts), NO_DATA, and EDGE pixels. 2165 self.log.
debug(
"Generating Count Image, and mask lists.")
2166 coaddBBox = templateCoadd.getBBox()
2167 slateIm = afwImage.ImageU(coaddBBox)
2168 epochCountImage = afwImage.ImageU(coaddBBox)
2169 nImage = afwImage.ImageU(coaddBBox)
2170 spanSetArtifactList = []
2171 spanSetNoDataMaskList = []
2172 spanSetEdgeList = []
2176 templateCoadd.mask.clearAllMaskPlanes()
2178 if self.config.doPreserveContainedBySource:
2179 templateFootprints = self.detectTemplate.detectFootprints(templateCoadd)
2181 templateFootprints =
None 2183 for warpRef, imageScaler
in zip(tempExpRefList, imageScalerList):
2185 if warpDiffExp
is not None:
2187 nImage.array += (numpy.isfinite(warpDiffExp.image.array) *
2188 ((warpDiffExp.mask.array & badPixelMask) == 0)).astype(numpy.uint16)
2189 fpSet = self.detect.detectFootprints(warpDiffExp, doSmooth=
False, clearMask=
True)
2190 fpSet.positive.merge(fpSet.negative)
2191 footprints = fpSet.positive
2193 spanSetList = [footprint.spans
for footprint
in footprints.getFootprints()]
2196 if self.config.doPrefilterArtifacts:
2198 for spans
in spanSetList:
2199 spans.setImage(slateIm, 1, doClip=
True)
2200 epochCountImage += slateIm
2206 nans = numpy.where(numpy.isnan(warpDiffExp.maskedImage.image.array), 1, 0)
2208 nansMask.setXY0(warpDiffExp.getXY0())
2209 edgeMask = warpDiffExp.mask
2210 spanSetEdgeMask = afwGeom.SpanSet.fromMask(edgeMask,
2211 edgeMask.getPlaneBitMask(
"EDGE")).split()
2215 nansMask = afwImage.MaskX(coaddBBox, 1)
2217 spanSetEdgeMask = []
2219 spanSetNoDataMask = afwGeom.SpanSet.fromMask(nansMask).split()
2221 spanSetNoDataMaskList.append(spanSetNoDataMask)
2222 spanSetArtifactList.append(spanSetList)
2223 spanSetEdgeList.append(spanSetEdgeMask)
2227 epochCountImage.writeFits(path)
2229 for i, spanSetList
in enumerate(spanSetArtifactList):
2231 filteredSpanSetList = self.
filterArtifacts(spanSetList, epochCountImage, nImage,
2233 spanSetArtifactList[i] = filteredSpanSetList
2236 for artifacts, noData, edge
in zip(spanSetArtifactList, spanSetNoDataMaskList, spanSetEdgeList):
2237 altMasks.append({
'CLIPPED': artifacts,
2243 """Remove artifact candidates covered by bad mask plane. 2245 Any future editing of the candidate list that does not depend on 2246 temporal information should go in this method. 2250 spanSetList : `list` 2251 List of SpanSets representing artifact candidates. 2252 exp : `lsst.afw.image.Exposure` 2253 Exposure containing mask planes used to prefilter. 2257 returnSpanSetList : `list` 2258 List of SpanSets with artifacts. 2260 badPixelMask = exp.mask.getPlaneBitMask(self.config.prefilterArtifactsMaskPlanes)
2261 goodArr = (exp.mask.array & badPixelMask) == 0
2262 returnSpanSetList = []
2263 bbox = exp.getBBox()
2264 x0, y0 = exp.getXY0()
2265 for i, span
in enumerate(spanSetList):
2266 y, x = span.clippedTo(bbox).indices()
2267 yIndexLocal = numpy.array(y) - y0
2268 xIndexLocal = numpy.array(x) - x0
2269 goodRatio = numpy.count_nonzero(goodArr[yIndexLocal, xIndexLocal])/span.getArea()
2270 if goodRatio > self.config.prefilterArtifactsRatio:
2271 returnSpanSetList.append(span)
2272 return returnSpanSetList
2274 def filterArtifacts(self, spanSetList, epochCountImage, nImage, footprintsToExclude=None):
2275 """Filter artifact candidates. 2279 spanSetList : `list` 2280 List of SpanSets representing artifact candidates. 2281 epochCountImage : `lsst.afw.image.Image` 2282 Image of accumulated number of warpDiff detections. 2283 nImage : `lsst.afw.image.Image` 2284 Image of the accumulated number of total epochs contributing. 2288 maskSpanSetList : `list` 2289 List of SpanSets with artifacts. 2292 maskSpanSetList = []
2293 x0, y0 = epochCountImage.getXY0()
2294 for i, span
in enumerate(spanSetList):
2295 y, x = span.indices()
2296 yIdxLocal = [y1 - y0
for y1
in y]
2297 xIdxLocal = [x1 - x0
for x1
in x]
2298 outlierN = epochCountImage.array[yIdxLocal, xIdxLocal]
2299 totalN = nImage.array[yIdxLocal, xIdxLocal]
2302 effMaxNumEpochsHighN = (self.config.maxNumEpochs +
2303 self.config.maxFractionEpochsHigh*numpy.mean(totalN))
2304 effMaxNumEpochsLowN = self.config.maxFractionEpochsLow * numpy.mean(totalN)
2305 effectiveMaxNumEpochs =
int(
min(effMaxNumEpochsLowN, effMaxNumEpochsHighN))
2306 nPixelsBelowThreshold = numpy.count_nonzero((outlierN > 0) &
2307 (outlierN <= effectiveMaxNumEpochs))
2308 percentBelowThreshold = nPixelsBelowThreshold / len(outlierN)
2309 if percentBelowThreshold > self.config.spatialThreshold:
2310 maskSpanSetList.append(span)
2312 if self.config.doPreserveContainedBySource
and footprintsToExclude
is not None:
2314 filteredMaskSpanSetList = []
2315 for span
in maskSpanSetList:
2317 for footprint
in footprintsToExclude.positive.getFootprints():
2318 if footprint.spans.contains(span):
2322 filteredMaskSpanSetList.append(span)
2323 maskSpanSetList = filteredMaskSpanSetList
2325 return maskSpanSetList
2327 def _readAndComputeWarpDiff(self, warpRef, imageScaler, templateCoadd):
2328 """Fetch a warp from the butler and return a warpDiff. 2332 warpRef : `lsst.daf.persistence.butlerSubset.ButlerDataRef` 2333 Butler dataRef for the warp. 2334 imageScaler : `lsst.pipe.tasks.scaleZeroPoint.ImageScaler` 2335 An image scaler object. 2336 templateCoadd : `lsst.afw.image.Exposure` 2337 Exposure to be substracted from the scaled warp. 2341 warp : `lsst.afw.image.Exposure` 2342 Exposure of the image difference between the warp and template. 2347 if not warpRef.datasetExists(warpName):
2348 self.log.
warn(
"Could not find %s %s; skipping it", warpName, warpRef.dataId)
2350 warp = warpRef.get(warpName, immediate=
True)
2352 imageScaler.scaleMaskedImage(warp.getMaskedImage())
2353 mi = warp.getMaskedImage()
2354 if self.config.doScaleWarpVariance:
2356 self.scaleWarpVariance.
run(mi)
2357 except Exception
as exc:
2358 self.log.
warn(
"Unable to rescale variance of warp (%s); leaving it as-is" % (exc,))
2359 mi -= templateCoadd.getMaskedImage()
2362 def _dataRef2DebugPath(self, prefix, warpRef, coaddLevel=False):
2363 """Return a path to which to write debugging output. 2365 Creates a hyphen-delimited string of dataId values for simple filenames. 2370 Prefix for filename. 2371 warpRef : `lsst.daf.persistence.butlerSubset.ButlerDataRef` 2372 Butler dataRef to make the path from. 2373 coaddLevel : `bool`, optional. 2374 If True, include only coadd-level keys (e.g., 'tract', 'patch', 2375 'filter', but no 'visit'). 2380 Path for debugging output. 2385 keys = warpRef.dataId.keys()
2386 keyList = sorted(keys, reverse=
True)
2388 filename =
"%s-%s.fits" % (prefix,
'-'.join([
str(warpRef.dataId[k])
for k
in keyList]))
2389 return os.path.join(directory, filename)
def setBrightObjectMasks(self, exposure, dataId, brightObjectMasks)
def shrinkValidPolygons(self, coaddInputs)
def getCoaddDatasetName(self, warpType="direct")
def _dataRef2DebugPath(self, prefix, warpRef, coaddLevel=False)
def getGroupDataRef(butler, datasetType, groupTuple, keys)
Base class for coaddition.
def findArtifacts(self, templateCoadd, tempExpRefList, imageScalerList)
A floating-point coordinate rectangle geometry.
def assembleMetadata(self, coaddExposure, tempExpRefList, weightList)
A compact representation of a collection of pixels.
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
A Threshold is used to pass a threshold value to detection algorithms.
def getTempExpRefList(self, patchRef, calExpRefList)
def removeMaskPlanes(self, maskedImage)
def makeSkyInfo(skyMap, tractId, patchId)
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
def _readAndComputeWarpDiff(self, warpRef, imageScaler, templateCoadd)
Fit spatial kernel using approximate fluxes for candidates, and solving a linear system of equations...
def prepareInputs(self, refList)
Statistics makeStatistics(lsst::afw::math::MaskedVector< EntryT > const &mv, std::vector< WeightPixel > const &vweights, int const flags, StatisticsControl const &sctrl=StatisticsControl())
The makeStatistics() overload to handle lsst::afw::math::MaskedVector<>
def applyAltMaskPlanes(self, mask, altMaskSpans)
Property stringToStatisticsProperty(std::string const property)
Conversion function to switch a string to a Property (see Statistics.h)
def getSkyInfo(self, patchRef)
Use getSkyinfo to return the skyMap, tract and patch information, wcs and the outer bbox of the patch...
def getTempExpDatasetName(self, warpType="direct")
bool all(CoordinateExpr< N > const &expr) noexcept
Return true if all elements are true.
def __init__(self, args, kwargs)
Pass parameters to a Statistics object.
def makeSupplementaryDataGen3(self, inputData, inputDataIds, outputDataIds, butler)
def prepareStats(self, mask=None)
def makeDataRefList(self, namespace)
def getBadPixelMask(self)
Convenience method to provide the bitmask from the mask plane names.
Represent a 2-dimensional array of bitmask pixels.
def assembleSubregion(self, coaddExposure, bbox, tempExpRefList, imageScalerList, weightList, altMaskList, statsFlags, statsCtrl, nImage=None)
def makeSupplementaryData(self, dataRef, selectDataList=None, warpRefList=None)
def detectClip(self, exp, tempExpRefList)
def setInexactPsf(self, mask)
def adaptArgsAndRun(self, inputData, inputDataIds, outputDataIds, butler)
def __init__(self, args, kwargs)
def makeSupplementaryData(self, dataRef, selectDataList=None, warpRefList=None)
def getPrerequisiteDatasetTypes(cls, config)
def filterArtifacts(self, spanSetList, epochCountImage, nImage, footprintsToExclude=None)
def run(self, skyInfo, tempExpRefList, imageScalerList, weightList, args, kwargs)
def run(self, skyInfo, tempExpRefList, imageScalerList, weightList, supplementaryData, args, kwargs)
def buildDifferenceImage(self, skyInfo, tempExpRefList, imageScalerList, weightList)
def _noTemplateMessage(self, warpType)
def selectExposures(self, patchRef, skyInfo=None, selectDataList=[])
Select exposures to coadd.
def setRejectedMaskMapping(statsCtrl)
def getOutputDatasetTypes(cls, config)
def applyAltEdgeMask(self, mask, altMaskList)
def getInputDatasetTypes(cls, config)
std::shared_ptr< image::MaskedImage< PixelT > > statisticsStack(image::MaskedImage< PixelT > const &image, Property flags, char dimension, StatisticsControl const &sctrl)
A function to compute statistics on the rows or columns of an image.
def makeSupplementaryDataGen3(self, inputData, inputDataIds, outputDataIds, butler)
def readBrightObjectMasks(self, dataRef)
def runDataRef(self, dataRef, selectDataList=None, warpRefList=None)
def processResults(self, coaddExposure, dataRef)
Reports invalid arguments.
std::vector< SchemaItem< Flag > > * items
def __init__(self, args, kwargs)
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects...
def prefilterArtifacts(self, spanSetList, exp)
def _subBBoxIter(bbox, subregionSize)
An integer coordinate rectangle.
daf::base::PropertyList * list
def run(self, skyInfo, tempExpRefList, imageScalerList, weightList, altMaskList=None, mask=None, supplementaryData=None)
def countMaskFromFootprint(mask, footprint, bitmask, ignoreMask)
def groupPatchExposures(patchDataRef, calexpDataRefList, coaddDatasetType="deepCoadd", tempExpDatasetType="deepCoadd_directWarp")
def detectClipBig(self, clipList, clipFootprints, clipIndices, detectionFootprints, maskClipValue, maskDetValue, coaddBBox)