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",
"AbstractFilter"),
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",
"AbstractFilter"),
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",
"AbstractFilter"),
195 self.formatTemplateNames({
"inputCoaddName":
"deep",
"outputCoaddName":
"deep",
197 self.quantum.dimensions = (
"Tract",
"Patch",
"AbstractFilter",
"SkyMap")
204 log.warn(
"Config doPsfMatch deprecated. Setting warpType='psfMatched'")
207 log.warn(
'doSigmaClip deprecated. To replicate behavior, setting statistic to "MEANCLIP"')
209 if self.
doInterp and self.
statistic not in [
'MEAN',
'MEDIAN',
'MEANCLIP',
'VARIANCE',
'VARIANCECLIP']:
210 raise ValueError(
"Must set doInterp=False for statistic=%s, which does not " 211 "compute and set a non-zero coadd variance estimate." % (self.
statistic))
213 unstackableStats = [
'NOTHING',
'ERROR',
'ORMASK']
214 if not hasattr(afwMath.Property, self.
statistic)
or self.
statistic in unstackableStats:
215 stackableStats = [
str(k)
for k
in afwMath.Property.__members__.keys()
216 if str(k)
not in unstackableStats]
217 raise ValueError(
"statistic %s is not allowed. Please choose one of %s." 222 """Assemble a coadded image from a set of warps (coadded temporary exposures). 224 We want to assemble a coadded image from a set of Warps (also called 225 coadded temporary exposures or ``coaddTempExps``). 226 Each input Warp covers a patch on the sky and corresponds to a single 227 run/visit/exposure of the covered patch. We provide the task with a list 228 of Warps (``selectDataList``) from which it selects Warps that cover the 229 specified patch (pointed at by ``dataRef``). 230 Each Warp that goes into a coadd will typically have an independent 231 photometric zero-point. Therefore, we must scale each Warp to set it to 232 a common photometric zeropoint. WarpType may be one of 'direct' or 233 'psfMatched', and the boolean configs `config.makeDirect` and 234 `config.makePsfMatched` set which of the warp types will be coadded. 235 The coadd is computed as a mean with optional outlier rejection. 236 Criteria for outlier rejection are set in `AssembleCoaddConfig`. 237 Finally, Warps can have bad 'NaN' pixels which received no input from the 238 source calExps. We interpolate over these bad (NaN) pixels. 240 `AssembleCoaddTask` uses several sub-tasks. These are 242 - `ScaleZeroPointTask` 243 - create and use an ``imageScaler`` object to scale the photometric zeropoint for each Warp 245 - interpolate across bad pixels (NaN) in the final coadd 247 You can retarget these subtasks if you wish. 251 The `lsst.pipe.base.cmdLineTask.CmdLineTask` interface supports a 252 flag ``-d`` to import ``debug.py`` from your ``PYTHONPATH``; see 253 `baseDebug` for more about ``debug.py`` files. `AssembleCoaddTask` has 254 no debug variables of its own. Some of the subtasks may support debug 255 variables. See the documentation for the subtasks for further information. 259 `AssembleCoaddTask` assembles a set of warped images into a coadded image. 260 The `AssembleCoaddTask` can be invoked by running ``assembleCoadd.py`` 261 with the flag '--legacyCoadd'. Usage of assembleCoadd.py expects two 262 inputs: a data reference to the tract patch and filter to be coadded, and 263 a list of Warps to attempt to coadd. These are specified using ``--id`` and 264 ``--selectId``, respectively: 268 --id = [KEY=VALUE1[^VALUE2[^VALUE3...] [KEY=VALUE1[^VALUE2[^VALUE3...] ...]] 269 --selectId [KEY=VALUE1[^VALUE2[^VALUE3...] [KEY=VALUE1[^VALUE2[^VALUE3...] ...]] 271 Only the Warps that cover the specified tract and patch will be coadded. 272 A list of the available optional arguments can be obtained by calling 273 ``assembleCoadd.py`` with the ``--help`` command line argument: 277 assembleCoadd.py --help 279 To demonstrate usage of the `AssembleCoaddTask` in the larger context of 280 multi-band processing, we will generate the HSC-I & -R band coadds from 281 HSC engineering test data provided in the ``ci_hsc`` package. To begin, 282 assuming that the lsst stack has been already set up, we must set up the 283 obs_subaru and ``ci_hsc`` packages. This defines the environment variable 284 ``$CI_HSC_DIR`` and points at the location of the package. The raw HSC 285 data live in the ``$CI_HSC_DIR/raw directory``. To begin assembling the 286 coadds, we must first 289 - process the individual ccds in $CI_HSC_RAW to produce calibrated exposures 291 - create a skymap that covers the area of the sky present in the raw exposures 293 - warp the individual calibrated exposures to the tangent plane of the coadd 295 We can perform all of these steps by running 299 $CI_HSC_DIR scons warp-903986 warp-904014 warp-903990 warp-904010 warp-903988 301 This will produce warped exposures for each visit. To coadd the warped 302 data, we call assembleCoadd.py as follows: 306 assembleCoadd.py --legacyCoadd $CI_HSC_DIR/DATA --id patch=5,4 tract=0 filter=HSC-I \ 307 --selectId visit=903986 ccd=16 --selectId visit=903986 ccd=22 --selectId visit=903986 ccd=23 \ 308 --selectId visit=903986 ccd=100 --selectId visit=904014 ccd=1 --selectId visit=904014 ccd=6 \ 309 --selectId visit=904014 ccd=12 --selectId visit=903990 ccd=18 --selectId visit=903990 ccd=25 \ 310 --selectId visit=904010 ccd=4 --selectId visit=904010 ccd=10 --selectId visit=904010 ccd=100 \ 311 --selectId visit=903988 ccd=16 --selectId visit=903988 ccd=17 --selectId visit=903988 ccd=23 \ 312 --selectId visit=903988 ccd=24 314 that will process the HSC-I band data. The results are written in 315 ``$CI_HSC_DIR/DATA/deepCoadd-results/HSC-I``. 317 You may also choose to run: 321 scons warp-903334 warp-903336 warp-903338 warp-903342 warp-903344 warp-903346 322 assembleCoadd.py --legacyCoadd $CI_HSC_DIR/DATA --id patch=5,4 tract=0 filter=HSC-R \ 323 --selectId visit=903334 ccd=16 --selectId visit=903334 ccd=22 --selectId visit=903334 ccd=23 \ 324 --selectId visit=903334 ccd=100 --selectId visit=903336 ccd=17 --selectId visit=903336 ccd=24 \ 325 --selectId visit=903338 ccd=18 --selectId visit=903338 ccd=25 --selectId visit=903342 ccd=4 \ 326 --selectId visit=903342 ccd=10 --selectId visit=903342 ccd=100 --selectId visit=903344 ccd=0 \ 327 --selectId visit=903344 ccd=5 --selectId visit=903344 ccd=11 --selectId visit=903346 ccd=1 \ 328 --selectId visit=903346 ccd=6 --selectId visit=903346 ccd=12 330 to generate the coadd for the HSC-R band if you are interested in 331 following multiBand Coadd processing as discussed in `pipeTasks_multiBand` 332 (but note that normally, one would use the `SafeClipAssembleCoaddTask` 333 rather than `AssembleCoaddTask` to make the coadd. 335 ConfigClass = AssembleCoaddConfig
336 _DefaultName =
"assembleCoadd" 341 argNames = [
"config",
"name",
"parentTask",
"log"]
342 kwargs.update({k: v
for k, v
in zip(argNames, args)})
343 warnings.warn(
"AssembleCoadd received positional args, and casting them as kwargs: %s. " 344 "PipelineTask will not take positional args" % argNames, FutureWarning)
347 self.makeSubtask(
"interpImage")
348 self.makeSubtask(
"scaleZeroPoint")
350 if self.config.doMaskBrightObjects:
354 except pexExceptions.LsstCppException:
355 raise RuntimeError(
"Unable to define mask plane for bright objects; planes used are %s" %
356 mask.getMaskPlaneDict().
keys())
363 """Return output dataset type descriptors 365 Remove output dataset types not produced by the Task 368 if not config.doNImage:
369 outputTypeDict.pop(
"nImage",
None)
370 return outputTypeDict
374 """Return input dataset type descriptors 376 Remove input dataset types not used by the Task 379 if not config.doMaskBrightObjects:
380 inputTypeDict.pop(
"brightObjectMask",
None)
384 """Assemble a coadd from a set of Warps. 386 PipelineTask (Gen3) entry point to Coadd a set of Warps. 387 Analogous to `runDataRef`, it prepares all the data products to be 388 passed to `run`, and processes the results before returning to struct 389 of results to be written out. AssembleCoadd cannot fit all Warps in memory. 390 Therefore, its inputs are accessed subregion by subregion 391 by the `lsst.daf.butler.ShimButler` that quacks like a Gen2 392 `lsst.daf.persistence.Butler`. Updates to this method should 393 correspond to an update in `runDataRef` while both entry points 399 Keys are the names of the configs describing input dataset types. 400 Values are input Python-domain data objects (or lists of objects) 401 retrieved from data butler. 402 inputDataIds : `dict` 403 Keys are the names of the configs describing input dataset types. 404 Values are DataIds (or lists of DataIds) that task consumes for 405 corresponding dataset type. 406 outputDataIds : `dict` 407 Keys are the names of the configs describing input dataset types. 408 Values are DataIds (or lists of DataIds) that task is to produce 409 for corresponding dataset type. 410 butler : `lsst.daf.butler.Butler` 411 Gen3 Butler object for fetching additional data products before 416 result : `lsst.pipe.base.Struct` 417 Result struct with components: 419 - ``coaddExposure`` : coadded exposure (``lsst.afw.image.Exposure``) 420 - ``nImage``: N Image (``lsst.afw.image.Image``) 424 skyMap = inputData[
"skyMap"]
425 outputDataId = next(iter(outputDataIds.values()))
427 tractId=outputDataId[
'tract'],
428 patchId=outputDataId[
'patch'])
432 warpRefList = [butlerShim.dataRef(self.config.inputWarps.name, dataId=dataId)
433 for dataId
in inputDataIds[
'inputWarps']]
436 patchRef = butlerShim.dataRef(self.config.coaddExposure.name, dataId=outputDataIds[
'coaddExposure'])
440 self.log.
info(
"Found %d %s", len(inputs.tempExpRefList),
442 if len(inputs.tempExpRefList) == 0:
443 self.log.
warn(
"No coadd temporary exposures found")
448 retStruct = self.
run(inputData[
'skyInfo'], inputs.tempExpRefList, inputs.imageScalerList,
449 inputs.weightList, supplementaryData=supplementaryData)
455 def runDataRef(self, dataRef, selectDataList=None, warpRefList=None):
456 """Assemble a coadd from a set of Warps. 458 Pipebase.CmdlineTask entry point to Coadd a set of Warps. 459 Compute weights to be applied to each Warp and 460 find scalings to match the photometric zeropoint to a reference Warp. 461 Assemble the Warps using `run`. Interpolate over NaNs and 462 optionally write the coadd to disk. Return the coadded exposure. 466 dataRef : `lsst.daf.persistence.butlerSubset.ButlerDataRef` 467 Data reference defining the patch for coaddition and the 468 reference Warp (if ``config.autoReference=False``). 469 Used to access the following data products: 470 - ``self.config.coaddName + "Coadd_skyMap"`` 471 - ``self.config.coaddName + "Coadd_ + <warpType> + "Warp"`` (optionally) 472 - ``self.config.coaddName + "Coadd"`` 473 selectDataList : `list` 474 List of data references to Calexps. Data to be coadded will be 475 selected from this list based on overlap with the patch defined 476 by dataRef, grouped by visit, and converted to a list of data 479 List of data references to Warps to be coadded. 480 Note: `warpRefList` is just the new name for `tempExpRefList`. 484 retStruct : `lsst.pipe.base.Struct` 485 Result struct with components: 487 - ``coaddExposure``: coadded exposure (``Exposure``). 488 - ``nImage``: exposure count image (``Image``). 490 if selectDataList
and warpRefList:
491 raise RuntimeError(
"runDataRef received both a selectDataList and warpRefList, " 492 "and which to use is ambiguous. Please pass only one.")
495 if warpRefList
is None:
496 calExpRefList = self.
selectExposures(dataRef, skyInfo, selectDataList=selectDataList)
497 if len(calExpRefList) == 0:
498 self.log.
warn(
"No exposures to coadd")
500 self.log.
info(
"Coadding %d exposures", len(calExpRefList))
505 self.log.
info(
"Found %d %s", len(inputData.tempExpRefList),
507 if len(inputData.tempExpRefList) == 0:
508 self.log.
warn(
"No coadd temporary exposures found")
513 retStruct = self.
run(skyInfo, inputData.tempExpRefList, inputData.imageScalerList,
514 inputData.weightList, supplementaryData=supplementaryData)
517 if self.config.doWrite:
520 if self.config.doNImage
and retStruct.nImage
is not None:
526 """Interpolate over missing data and mask bright stars. 530 coaddExposure : `lsst.afw.image.Exposure` 531 The coadded exposure to process. 532 dataRef : `lsst.daf.persistence.ButlerDataRef` 533 Butler data reference for supplementary data. 535 if self.config.doInterp:
536 self.interpImage.
run(coaddExposure.getMaskedImage(), planeName=
"NO_DATA")
538 varArray = coaddExposure.variance.array
539 with numpy.errstate(invalid=
"ignore"):
540 varArray[:] = numpy.where(varArray > 0, varArray, numpy.inf)
542 if self.config.doMaskBrightObjects:
547 """Make additional inputs to run() specific to subclasses (Gen2) 549 Duplicates interface of `runDataRef` method 550 Available to be implemented by subclasses only if they need the 551 coadd dataRef for performing preliminary processing before 552 assembling the coadd. 556 dataRef : `lsst.daf.persistence.ButlerDataRef` 557 Butler data reference for supplementary data. 558 selectDataList : `list` 559 List of data references to Warps. 564 """Make additional inputs to run() specific to subclasses (Gen3) 566 Duplicates interface of`adaptArgsAndRun` method. 567 Available to be implemented by subclasses only if they need the 568 coadd dataRef for performing preliminary processing before 569 assembling the coadd. 574 Keys are the names of the configs describing input dataset types. 575 Values are input Python-domain data objects (or lists of objects) 576 retrieved from data butler. 577 inputDataIds : `dict` 578 Keys are the names of the configs describing input dataset types. 579 Values are DataIds (or lists of DataIds) that task consumes for 580 corresponding dataset type. 581 DataIds are guaranteed to match data objects in ``inputData``. 582 outputDataIds : `dict` 583 Keys are the names of the configs describing input dataset types. 584 Values are DataIds (or lists of DataIds) that task is to produce 585 for corresponding dataset type. 586 butler : `lsst.daf.butler.Butler` 587 Gen3 Butler object for fetching additional data products before 592 result : `lsst.pipe.base.Struct` 593 Contains whatever additional data the subclass's `run` method needs 598 """Generate list data references corresponding to warped exposures 599 that lie within the patch to be coadded. 604 Data reference for patch. 605 calExpRefList : `list` 606 List of data references for input calexps. 610 tempExpRefList : `list` 611 List of Warp/CoaddTempExp data references. 613 butler = patchRef.getButler()
614 groupData =
groupPatchExposures(patchRef, calExpRefList, self.getCoaddDatasetName(self.warpType),
615 self.getTempExpDatasetName(self.warpType))
616 tempExpRefList = [
getGroupDataRef(butler, self.getTempExpDatasetName(self.warpType),
617 g, groupData.keys)
for 618 g
in groupData.groups.keys()]
619 return tempExpRefList
622 """Prepare the input warps for coaddition by measuring the weight for 623 each warp and the scaling for the photometric zero point. 625 Each Warp has its own photometric zeropoint and background variance. 626 Before coadding these Warps together, compute a scale factor to 627 normalize the photometric zeropoint and compute the weight for each Warp. 632 List of data references to tempExp 636 result : `lsst.pipe.base.Struct` 637 Result struct with components: 639 - ``tempExprefList``: `list` of data references to tempExp. 640 - ``weightList``: `list` of weightings. 641 - ``imageScalerList``: `list` of image scalers. 644 statsCtrl.setNumSigmaClip(self.config.sigmaClip)
645 statsCtrl.setNumIter(self.config.clipIter)
647 statsCtrl.setNanSafe(
True)
655 for tempExpRef
in refList:
656 if not tempExpRef.datasetExists(tempExpName):
657 self.log.
warn(
"Could not find %s %s; skipping it", tempExpName, tempExpRef.dataId)
660 tempExp = tempExpRef.get(tempExpName, immediate=
True)
662 if numpy.isnan(tempExp.image.array).
all():
664 maskedImage = tempExp.getMaskedImage()
665 imageScaler = self.scaleZeroPoint.computeImageScaler(
670 imageScaler.scaleMaskedImage(maskedImage)
671 except Exception
as e:
672 self.log.
warn(
"Scaling failed for %s (skipping it): %s", tempExpRef.dataId, e)
675 afwMath.MEANCLIP, statsCtrl)
676 meanVar, meanVarErr = statObj.getResult(afwMath.MEANCLIP)
677 weight = 1.0 /
float(meanVar)
678 if not numpy.isfinite(weight):
679 self.log.
warn(
"Non-finite weight for %s: skipping", tempExpRef.dataId)
681 self.log.
info(
"Weight of %s %s = %0.3f", tempExpName, tempExpRef.dataId, weight)
686 tempExpRefList.append(tempExpRef)
687 weightList.append(weight)
688 imageScalerList.append(imageScaler)
690 return pipeBase.Struct(tempExpRefList=tempExpRefList, weightList=weightList,
691 imageScalerList=imageScalerList)
694 """Prepare the statistics for coadding images. 698 mask : `int`, optional 699 Bit mask value to exclude from coaddition. 703 stats : `lsst.pipe.base.Struct` 704 Statistics structure with the following fields: 706 - ``statsCtrl``: Statistics control object for coadd 707 (`lsst.afw.math.StatisticsControl`) 708 - ``statsFlags``: Statistic for coadd (`lsst.afw.math.Property`) 713 statsCtrl.setNumSigmaClip(self.config.sigmaClip)
714 statsCtrl.setNumIter(self.config.clipIter)
715 statsCtrl.setAndMask(mask)
716 statsCtrl.setNanSafe(
True)
717 statsCtrl.setWeighted(
True)
718 statsCtrl.setCalcErrorFromInputVariance(self.config.calcErrorFromInputVariance)
719 for plane, threshold
in self.config.maskPropagationThresholds.items():
720 bit = afwImage.Mask.getMaskPlane(plane)
721 statsCtrl.setMaskPropagationThreshold(bit, threshold)
723 return pipeBase.Struct(ctrl=statsCtrl, flags=statsFlags)
725 def run(self, skyInfo, tempExpRefList, imageScalerList, weightList,
726 altMaskList=None, mask=None, supplementaryData=None):
727 """Assemble a coadd from input warps 729 Assemble the coadd using the provided list of coaddTempExps. Since 730 the full coadd covers a patch (a large area), the assembly is 731 performed over small areas on the image at a time in order to 732 conserve memory usage. Iterate over subregions within the outer 733 bbox of the patch using `assembleSubregion` to stack the corresponding 734 subregions from the coaddTempExps with the statistic specified. 735 Set the edge bits the coadd mask based on the weight map. 739 skyInfo : `lsst.pipe.base.Struct` 740 Struct with geometric information about the patch. 741 tempExpRefList : `list` 742 List of data references to Warps (previously called CoaddTempExps). 743 imageScalerList : `list` 744 List of image scalers. 747 altMaskList : `list`, optional 748 List of alternate masks to use rather than those stored with 750 mask : `int`, optional 751 Bit mask value to exclude from coaddition. 752 supplementaryData : lsst.pipe.base.Struct, optional 753 Struct with additional data products needed to assemble coadd. 754 Only used by subclasses that implement `makeSupplementaryData` 759 result : `lsst.pipe.base.Struct` 760 Result struct with components: 762 - ``coaddExposure``: coadded exposure (``lsst.afw.image.Exposure``). 763 - ``nImage``: exposure count image (``lsst.afw.image.Image``). 766 self.log.
info(
"Assembling %s %s", len(tempExpRefList), tempExpName)
769 if altMaskList
is None:
770 altMaskList = [
None]*len(tempExpRefList)
772 coaddExposure = afwImage.ExposureF(skyInfo.bbox, skyInfo.wcs)
773 coaddExposure.setCalib(self.scaleZeroPoint.getCalib())
774 coaddExposure.getInfo().setCoaddInputs(self.inputRecorder.makeCoaddInputs())
776 coaddMaskedImage = coaddExposure.getMaskedImage()
777 subregionSizeArr = self.config.subregionSize
780 if self.config.doNImage:
781 nImage = afwImage.ImageU(skyInfo.bbox)
784 for subBBox
in self.
_subBBoxIter(skyInfo.bbox, subregionSize):
787 weightList, altMaskList, stats.flags, stats.ctrl,
789 except Exception
as e:
790 self.log.
fatal(
"Cannot compute coadd %s: %s", subBBox, e)
796 return pipeBase.Struct(coaddExposure=coaddExposure, nImage=nImage)
799 """Set the metadata for the coadd. 801 This basic implementation sets the filter from the first input. 805 coaddExposure : `lsst.afw.image.Exposure` 806 The target exposure for the coadd. 807 tempExpRefList : `list` 808 List of data references to tempExp. 812 assert len(tempExpRefList) == len(weightList),
"Length mismatch" 817 tempExpList = [tempExpRef.get(tempExpName +
"_sub",
820 for tempExpRef
in tempExpRefList]
821 numCcds = sum(len(tempExp.getInfo().getCoaddInputs().ccds)
for tempExp
in tempExpList)
823 coaddExposure.setFilter(tempExpList[0].getFilter())
824 coaddInputs = coaddExposure.getInfo().getCoaddInputs()
825 coaddInputs.ccds.reserve(numCcds)
826 coaddInputs.visits.reserve(len(tempExpList))
828 for tempExp, weight
in zip(tempExpList, weightList):
829 self.inputRecorder.addVisitToCoadd(coaddInputs, tempExp, weight)
831 if self.config.doUsePsfMatchedPolygons:
834 coaddInputs.visits.sort()
840 modelPsfList = [tempExp.getPsf()
for tempExp
in tempExpList]
841 modelPsfWidthList = [modelPsf.computeBBox().getWidth()
for modelPsf
in modelPsfList]
842 psf = modelPsfList[modelPsfWidthList.index(
max(modelPsfWidthList))]
844 psf = measAlg.CoaddPsf(coaddInputs.ccds, coaddExposure.getWcs(),
845 self.config.coaddPsf.makeControl())
846 coaddExposure.setPsf(psf)
847 apCorrMap = measAlg.makeCoaddApCorrMap(coaddInputs.ccds, coaddExposure.getBBox(afwImage.PARENT),
848 coaddExposure.getWcs())
849 coaddExposure.getInfo().setApCorrMap(apCorrMap)
850 if self.config.doAttachTransmissionCurve:
851 transmissionCurve = measAlg.makeCoaddTransmissionCurve(coaddExposure.getWcs(), coaddInputs.ccds)
852 coaddExposure.getInfo().setTransmissionCurve(transmissionCurve)
854 def assembleSubregion(self, coaddExposure, bbox, tempExpRefList, imageScalerList, weightList,
855 altMaskList, statsFlags, statsCtrl, nImage=None):
856 """Assemble the coadd for a sub-region. 858 For each coaddTempExp, check for (and swap in) an alternative mask 859 if one is passed. Remove mask planes listed in 860 `config.removeMaskPlanes`. Finally, stack the actual exposures using 861 `lsst.afw.math.statisticsStack` with the statistic specified by 862 statsFlags. Typically, the statsFlag will be one of lsst.afw.math.MEAN for 863 a mean-stack or `lsst.afw.math.MEANCLIP` for outlier rejection using 864 an N-sigma clipped mean where N and iterations are specified by 865 statsCtrl. Assign the stacked subregion back to the coadd. 869 coaddExposure : `lsst.afw.image.Exposure` 870 The target exposure for the coadd. 871 bbox : `lsst.afw.geom.Box` 873 tempExpRefList : `list` 874 List of data reference to tempExp. 875 imageScalerList : `list` 876 List of image scalers. 880 List of alternate masks to use rather than those stored with 881 tempExp, or None. Each element is dict with keys = mask plane 882 name to which to add the spans. 883 statsFlags : `lsst.afw.math.Property` 884 Property object for statistic for coadd. 885 statsCtrl : `lsst.afw.math.StatisticsControl` 886 Statistics control object for coadd. 887 nImage : `lsst.afw.image.ImageU`, optional 888 Keeps track of exposure count for each pixel. 890 self.log.
debug(
"Computing coadd over %s", bbox)
892 coaddExposure.mask.addMaskPlane(
"REJECTED")
893 coaddExposure.mask.addMaskPlane(
"CLIPPED")
894 coaddExposure.mask.addMaskPlane(
"SENSOR_EDGE")
896 clipped = afwImage.Mask.getPlaneBitMask(
"CLIPPED")
898 if nImage
is not None:
899 subNImage = afwImage.ImageU(bbox.getWidth(), bbox.getHeight())
900 for tempExpRef, imageScaler, altMask
in zip(tempExpRefList, imageScalerList, altMaskList):
901 exposure = tempExpRef.get(tempExpName +
"_sub", bbox=bbox)
902 maskedImage = exposure.getMaskedImage()
903 mask = maskedImage.getMask()
904 if altMask
is not None:
906 imageScaler.scaleMaskedImage(maskedImage)
910 if nImage
is not None:
911 subNImage.getArray()[maskedImage.getMask().getArray() & statsCtrl.getAndMask() == 0] += 1
912 if self.config.removeMaskPlanes:
914 maskedImageList.append(maskedImage)
916 with self.timer(
"stack"):
920 coaddExposure.maskedImage.assign(coaddSubregion, bbox)
921 if nImage
is not None:
922 nImage.assign(subNImage, bbox)
925 """Unset the mask of an image for mask planes specified in the config. 929 maskedImage : `lsst.afw.image.MaskedImage` 930 The masked image to be modified. 932 mask = maskedImage.getMask()
933 for maskPlane
in self.config.removeMaskPlanes:
935 mask &= ~mask.getPlaneBitMask(maskPlane)
936 except Exception
as e:
937 self.log.
warn(
"Unable to remove mask plane %s: %s", maskPlane, e.args[0])
941 """Map certain mask planes of the warps to new planes for the coadd. 943 If a pixel is rejected due to a mask value other than EDGE, NO_DATA, 944 or CLIPPED, set it to REJECTED on the coadd. 945 If a pixel is rejected due to EDGE, set the coadd pixel to SENSOR_EDGE. 946 If a pixel is rejected due to CLIPPED, set the coadd pixel to CLIPPED. 950 statsCtrl : `lsst.afw.math.StatisticsControl` 951 Statistics control object for coadd 955 maskMap : `list` of `tuple` of `int` 956 A list of mappings of mask planes of the warped exposures to 957 mask planes of the coadd. 959 edge = afwImage.Mask.getPlaneBitMask(
"EDGE")
960 noData = afwImage.Mask.getPlaneBitMask(
"NO_DATA")
961 clipped = afwImage.Mask.getPlaneBitMask(
"CLIPPED")
962 toReject = statsCtrl.getAndMask() & (~noData) & (~edge) & (~clipped)
963 maskMap = [(toReject, afwImage.Mask.getPlaneBitMask(
"REJECTED")),
964 (edge, afwImage.Mask.getPlaneBitMask(
"SENSOR_EDGE")),
969 """Apply in place alt mask formatted as SpanSets to a mask. 973 mask : `lsst.afw.image.Mask` 975 altMaskSpans : `dict` 976 SpanSet lists to apply. Each element contains the new mask 977 plane name (e.g. "CLIPPED and/or "NO_DATA") as the key, 978 and list of SpanSets to apply to the mask. 982 mask : `lsst.afw.image.Mask` 985 if self.config.doUsePsfMatchedPolygons:
986 if (
"NO_DATA" in altMaskSpans)
and (
"NO_DATA" in self.config.badMaskPlanes):
991 for spanSet
in altMaskSpans[
'NO_DATA']:
992 spanSet.clippedTo(mask.getBBox()).clearMask(mask, self.
getBadPixelMask())
994 for plane, spanSetList
in altMaskSpans.items():
995 maskClipValue = mask.addMaskPlane(plane)
996 for spanSet
in spanSetList:
997 spanSet.clippedTo(mask.getBBox()).setMask(mask, 2**maskClipValue)
1001 """Shrink coaddInputs' ccds' ValidPolygons in place. 1003 Either modify each ccd's validPolygon in place, or if CoaddInputs 1004 does not have a validPolygon, create one from its bbox. 1008 coaddInputs : `lsst.afw.image.coaddInputs` 1012 for ccd
in coaddInputs.ccds:
1013 polyOrig = ccd.getValidPolygon()
1014 validPolyBBox = polyOrig.getBBox()
if polyOrig
else ccd.getBBox()
1015 validPolyBBox.grow(-self.config.matchingKernelSize//2)
1017 validPolygon = polyOrig.intersectionSingle(validPolyBBox)
1019 validPolygon = afwGeom.polygon.Polygon(
afwGeom.Box2D(validPolyBBox))
1020 ccd.setValidPolygon(validPolygon)
1023 """Retrieve the bright object masks. 1025 Returns None on failure. 1029 dataRef : `lsst.daf.persistence.butlerSubset.ButlerDataRef` 1034 result : `lsst.daf.persistence.butlerSubset.ButlerDataRef` 1035 Bright object mask from the Butler object, or None if it cannot 1039 return dataRef.get(
"brightObjectMask", immediate=
True)
1040 except Exception
as e:
1041 self.log.
warn(
"Unable to read brightObjectMask for %s: %s", dataRef.dataId, e)
1045 """Set the bright object masks. 1049 exposure : `lsst.afw.image.Exposure` 1050 Exposure under consideration. 1051 dataId : `lsst.daf.persistence.dataId` 1052 Data identifier dict for patch. 1053 brightObjectMasks : `lsst.afw.table` 1054 Table of bright objects to mask. 1057 if brightObjectMasks
is None:
1058 self.log.
warn(
"Unable to apply bright object mask: none supplied")
1060 self.log.
info(
"Applying %d bright object masks to %s", len(brightObjectMasks), dataId)
1061 mask = exposure.getMaskedImage().getMask()
1062 wcs = exposure.getWcs()
1063 plateScale = wcs.getPixelScale().asArcseconds()
1065 for rec
in brightObjectMasks:
1067 if rec[
"type"] ==
"box":
1068 assert rec[
"angle"] == 0.0, (
"Angle != 0 for mask object %s" % rec[
"id"])
1069 width = rec[
"width"].asArcseconds()/plateScale
1070 height = rec[
"height"].asArcseconds()/plateScale
1078 elif rec[
"type"] ==
"circle":
1079 radius =
int(rec[
"radius"].asArcseconds()/plateScale)
1080 spans = afwGeom.SpanSet.fromShape(radius, offset=center)
1082 self.log.
warn(
"Unexpected region type %s at %s" % rec[
"type"], center)
1087 """Set INEXACT_PSF mask plane. 1089 If any of the input images isn't represented in the coadd (due to 1090 clipped pixels or chip gaps), the `CoaddPsf` will be inexact. Flag 1095 mask : `lsst.afw.image.Mask` 1096 Coadded exposure's mask, modified in-place. 1098 mask.addMaskPlane(
"INEXACT_PSF")
1099 inexactPsf = mask.getPlaneBitMask(
"INEXACT_PSF")
1100 sensorEdge = mask.getPlaneBitMask(
"SENSOR_EDGE")
1101 clipped = mask.getPlaneBitMask(
"CLIPPED")
1102 rejected = mask.getPlaneBitMask(
"REJECTED")
1103 array = mask.getArray()
1104 selected = array & (sensorEdge | clipped | rejected) > 0
1105 array[selected] |= inexactPsf
1108 def _makeArgumentParser(cls):
1109 """Create an argument parser. 1111 parser = pipeBase.ArgumentParser(name=cls.
_DefaultName)
1112 parser.add_id_argument(
"--id", cls.
ConfigClass().coaddName +
"Coadd_" +
1114 help=
"data ID, e.g. --id tract=12345 patch=1,2",
1115 ContainerClass=AssembleCoaddDataIdContainer)
1116 parser.add_id_argument(
"--selectId",
"calexp", help=
"data ID, e.g. --selectId visit=6789 ccd=0..9",
1117 ContainerClass=SelectDataIdContainer)
1121 def _subBBoxIter(bbox, subregionSize):
1122 """Iterate over subregions of a bbox. 1126 bbox : `lsst.afw.geom.Box2I` 1127 Bounding box over which to iterate. 1128 subregionSize: `lsst.afw.geom.Extent2I` 1133 subBBox : `lsst.afw.geom.Box2I` 1134 Next sub-bounding box of size ``subregionSize`` or smaller; each ``subBBox`` 1135 is contained within ``bbox``, so it may be smaller than ``subregionSize`` at 1136 the edges of ``bbox``, but it will never be empty. 1139 raise RuntimeError(
"bbox %s is empty" % (bbox,))
1140 if subregionSize[0] < 1
or subregionSize[1] < 1:
1141 raise RuntimeError(
"subregionSize %s must be nonzero" % (subregionSize,))
1143 for rowShift
in range(0, bbox.getHeight(), subregionSize[1]):
1144 for colShift
in range(0, bbox.getWidth(), subregionSize[0]):
1147 if subBBox.isEmpty():
1148 raise RuntimeError(
"Bug: empty bbox! bbox=%s, subregionSize=%s, " 1149 "colShift=%s, rowShift=%s" %
1150 (bbox, subregionSize, colShift, rowShift))
1155 """A version of `lsst.pipe.base.DataIdContainer` specialized for assembleCoadd. 1159 """Make self.refList from self.idList. 1164 Results of parsing command-line (with ``butler`` and ``log`` elements). 1166 datasetType = namespace.config.coaddName +
"Coadd" 1167 keysCoadd = namespace.butler.getKeys(datasetType=datasetType, level=self.level)
1169 for dataId
in self.idList:
1171 for key
in keysCoadd:
1172 if key
not in dataId:
1173 raise RuntimeError(
"--id must include " + key)
1175 dataRef = namespace.butler.dataRef(
1176 datasetType=datasetType,
1179 self.refList.
append(dataRef)
1183 """Function to count the number of pixels with a specific mask in a 1186 Find the intersection of mask & footprint. Count all pixels in the mask 1187 that are in the intersection that have bitmask set but do not have 1188 ignoreMask set. Return the count. 1192 mask : `lsst.afw.image.Mask` 1193 Mask to define intersection region by. 1194 footprint : `lsst.afw.detection.Footprint` 1195 Footprint to define the intersection region by. 1197 Specific mask that we wish to count the number of occurances of. 1199 Pixels to not consider. 1204 Count of number of pixels in footprint with specified mask. 1206 bbox = footprint.getBBox()
1207 bbox.clip(mask.getBBox(afwImage.PARENT))
1209 subMask = mask.Factory(mask, bbox, afwImage.PARENT)
1210 footprint.spans.setMask(fp, bitmask)
1211 return numpy.logical_and((subMask.getArray() & fp.getArray()) > 0,
1212 (subMask.getArray() & ignoreMask) == 0).sum()
1216 """Configuration parameters for the SafeClipAssembleCoaddTask. 1218 clipDetection = pexConfig.ConfigurableField(
1219 target=SourceDetectionTask,
1220 doc=
"Detect sources on difference between unclipped and clipped coadd")
1221 minClipFootOverlap = pexConfig.Field(
1222 doc=
"Minimum fractional overlap of clipped footprint with visit DETECTED to be clipped",
1226 minClipFootOverlapSingle = pexConfig.Field(
1227 doc=
"Minimum fractional overlap of clipped footprint with visit DETECTED to be " 1228 "clipped when only one visit overlaps",
1232 minClipFootOverlapDouble = pexConfig.Field(
1233 doc=
"Minimum fractional overlap of clipped footprints with visit DETECTED to be " 1234 "clipped when two visits overlap",
1238 maxClipFootOverlapDouble = pexConfig.Field(
1239 doc=
"Maximum fractional overlap of clipped footprints with visit DETECTED when " 1240 "considering two visits",
1244 minBigOverlap = pexConfig.Field(
1245 doc=
"Minimum number of pixels in footprint to use DETECTED mask from the single visits " 1246 "when labeling clipped footprints",
1252 """Set default values for clipDetection. 1256 The numeric values for these configuration parameters were 1257 empirically determined, future work may further refine them. 1259 AssembleCoaddConfig.setDefaults(self)
1275 log.warn(
"Additional Sigma-clipping not allowed in Safe-clipped Coadds. " 1276 "Ignoring doSigmaClip.")
1279 raise ValueError(
"Only MEAN statistic allowed for final stacking in SafeClipAssembleCoadd " 1280 "(%s chosen). Please set statistic to MEAN." 1282 AssembleCoaddTask.ConfigClass.validate(self)
1286 """Assemble a coadded image from a set of coadded temporary exposures, 1287 being careful to clip & flag areas with potential artifacts. 1289 In ``AssembleCoaddTask``, we compute the coadd as an clipped mean (i.e., 1290 we clip outliers). The problem with doing this is that when computing the 1291 coadd PSF at a given location, individual visit PSFs from visits with 1292 outlier pixels contribute to the coadd PSF and cannot be treated correctly. 1293 In this task, we correct for this behavior by creating a new 1294 ``badMaskPlane`` 'CLIPPED'. We populate this plane on the input 1295 coaddTempExps and the final coadd where 1297 i. difference imaging suggests that there is an outlier and 1298 ii. this outlier appears on only one or two images. 1300 Such regions will not contribute to the final coadd. Furthermore, any 1301 routine to determine the coadd PSF can now be cognizant of clipped regions. 1302 Note that the algorithm implemented by this task is preliminary and works 1303 correctly for HSC data. Parameter modifications and or considerable 1304 redesigning of the algorithm is likley required for other surveys. 1306 ``SafeClipAssembleCoaddTask`` uses a ``SourceDetectionTask`` 1307 "clipDetection" subtask and also sub-classes ``AssembleCoaddTask``. 1308 You can retarget the ``SourceDetectionTask`` "clipDetection" subtask 1313 The `lsst.pipe.base.cmdLineTask.CmdLineTask` interface supports a 1314 flag ``-d`` to import ``debug.py`` from your ``PYTHONPATH``; 1315 see `baseDebug` for more about ``debug.py`` files. 1316 `SafeClipAssembleCoaddTask` has no debug variables of its own. 1317 The ``SourceDetectionTask`` "clipDetection" subtasks may support debug 1318 variables. See the documetation for `SourceDetectionTask` "clipDetection" 1319 for further information. 1323 `SafeClipAssembleCoaddTask` assembles a set of warped ``coaddTempExp`` 1324 images into a coadded image. The `SafeClipAssembleCoaddTask` is invoked by 1325 running assembleCoadd.py *without* the flag '--legacyCoadd'. 1327 Usage of ``assembleCoadd.py`` expects a data reference to the tract patch 1328 and filter to be coadded (specified using 1329 '--id = [KEY=VALUE1[^VALUE2[^VALUE3...] [KEY=VALUE1[^VALUE2[^VALUE3...] ...]]') 1330 along with a list of coaddTempExps to attempt to coadd (specified using 1331 '--selectId [KEY=VALUE1[^VALUE2[^VALUE3...] [KEY=VALUE1[^VALUE2[^VALUE3...] ...]]'). 1332 Only the coaddTempExps that cover the specified tract and patch will be 1333 coadded. A list of the available optional arguments can be obtained by 1334 calling assembleCoadd.py with the --help command line argument: 1336 .. code-block:: none 1338 assembleCoadd.py --help 1340 To demonstrate usage of the `SafeClipAssembleCoaddTask` in the larger 1341 context of multi-band processing, we will generate the HSC-I & -R band 1342 coadds from HSC engineering test data provided in the ci_hsc package. 1343 To begin, assuming that the lsst stack has been already set up, we must 1344 set up the obs_subaru and ci_hsc packages. This defines the environment 1345 variable $CI_HSC_DIR and points at the location of the package. The raw 1346 HSC data live in the ``$CI_HSC_DIR/raw`` directory. To begin assembling 1347 the coadds, we must first 1350 process the individual ccds in $CI_HSC_RAW to produce calibrated exposures 1352 create a skymap that covers the area of the sky present in the raw exposures 1353 - ``makeCoaddTempExp`` 1354 warp the individual calibrated exposures to the tangent plane of the coadd</DD> 1356 We can perform all of these steps by running 1358 .. code-block:: none 1360 $CI_HSC_DIR scons warp-903986 warp-904014 warp-903990 warp-904010 warp-903988 1362 This will produce warped coaddTempExps for each visit. To coadd the 1363 warped data, we call ``assembleCoadd.py`` as follows: 1365 .. code-block:: none 1367 assembleCoadd.py $CI_HSC_DIR/DATA --id patch=5,4 tract=0 filter=HSC-I \ 1368 --selectId visit=903986 ccd=16 --selectId visit=903986 ccd=22 --selectId visit=903986 ccd=23 \ 1369 --selectId visit=903986 ccd=100--selectId visit=904014 ccd=1 --selectId visit=904014 ccd=6 \ 1370 --selectId visit=904014 ccd=12 --selectId visit=903990 ccd=18 --selectId visit=903990 ccd=25 \ 1371 --selectId visit=904010 ccd=4 --selectId visit=904010 ccd=10 --selectId visit=904010 ccd=100 \ 1372 --selectId visit=903988 ccd=16 --selectId visit=903988 ccd=17 --selectId visit=903988 ccd=23 \ 1373 --selectId visit=903988 ccd=24 1375 This will process the HSC-I band data. The results are written in 1376 ``$CI_HSC_DIR/DATA/deepCoadd-results/HSC-I``. 1378 You may also choose to run: 1380 .. code-block:: none 1382 scons warp-903334 warp-903336 warp-903338 warp-903342 warp-903344 warp-903346 nnn 1383 assembleCoadd.py $CI_HSC_DIR/DATA --id patch=5,4 tract=0 filter=HSC-R --selectId visit=903334 ccd=16 \ 1384 --selectId visit=903334 ccd=22 --selectId visit=903334 ccd=23 --selectId visit=903334 ccd=100 \ 1385 --selectId visit=903336 ccd=17 --selectId visit=903336 ccd=24 --selectId visit=903338 ccd=18 \ 1386 --selectId visit=903338 ccd=25 --selectId visit=903342 ccd=4 --selectId visit=903342 ccd=10 \ 1387 --selectId visit=903342 ccd=100 --selectId visit=903344 ccd=0 --selectId visit=903344 ccd=5 \ 1388 --selectId visit=903344 ccd=11 --selectId visit=903346 ccd=1 --selectId visit=903346 ccd=6 \ 1389 --selectId visit=903346 ccd=12 1391 to generate the coadd for the HSC-R band if you are interested in following 1392 multiBand Coadd processing as discussed in ``pipeTasks_multiBand``. 1394 ConfigClass = SafeClipAssembleCoaddConfig
1395 _DefaultName =
"safeClipAssembleCoadd" 1398 AssembleCoaddTask.__init__(self, *args, **kwargs)
1399 schema = afwTable.SourceTable.makeMinimalSchema()
1400 self.makeSubtask(
"clipDetection", schema=schema)
1402 def run(self, skyInfo, tempExpRefList, imageScalerList, weightList, *args, **kwargs):
1403 """Assemble the coadd for a region. 1405 Compute the difference of coadds created with and without outlier 1406 rejection to identify coadd pixels that have outlier values in some 1408 Detect clipped regions on the difference image and mark these regions 1409 on the one or two individual coaddTempExps where they occur if there 1410 is significant overlap between the clipped region and a source. This 1411 leaves us with a set of footprints from the difference image that have 1412 been identified as having occured on just one or two individual visits. 1413 However, these footprints were generated from a difference image. It 1414 is conceivable for a large diffuse source to have become broken up 1415 into multiple footprints acrosss the coadd difference in this process. 1416 Determine the clipped region from all overlapping footprints from the 1417 detected sources in each visit - these are big footprints. 1418 Combine the small and big clipped footprints and mark them on a new 1420 Generate the coadd using `AssembleCoaddTask.run` without outlier 1421 removal. Clipped footprints will no longer make it into the coadd 1422 because they are marked in the new bad mask plane. 1426 skyInfo : `lsst.pipe.base.Struct` 1427 Patch geometry information, from getSkyInfo 1428 tempExpRefList : `list` 1429 List of data reference to tempExp 1430 imageScalerList : `list` 1431 List of image scalers 1437 result : `lsst.pipe.base.Struct` 1438 Result struct with components: 1440 - ``coaddExposure``: coadded exposure (``lsst.afw.image.Exposure``). 1441 - ``nImage``: exposure count image (``lsst.afw.image.Image``). 1445 args and kwargs are passed but ignored in order to match the call 1446 signature expected by the parent task. 1449 mask = exp.getMaskedImage().getMask()
1450 mask.addMaskPlane(
"CLIPPED")
1452 result = self.
detectClip(exp, tempExpRefList)
1454 self.log.
info(
'Found %d clipped objects', len(result.clipFootprints))
1456 maskClipValue = mask.getPlaneBitMask(
"CLIPPED")
1457 maskDetValue = mask.getPlaneBitMask(
"DETECTED") | mask.getPlaneBitMask(
"DETECTED_NEGATIVE")
1459 bigFootprints = self.
detectClipBig(result.clipSpans, result.clipFootprints, result.clipIndices,
1460 result.detectionFootprints, maskClipValue, maskDetValue,
1463 maskClip = mask.Factory(mask.getBBox(afwImage.PARENT))
1464 afwDet.setMaskFromFootprintList(maskClip, result.clipFootprints, maskClipValue)
1466 maskClipBig = maskClip.Factory(mask.getBBox(afwImage.PARENT))
1467 afwDet.setMaskFromFootprintList(maskClipBig, bigFootprints, maskClipValue)
1468 maskClip |= maskClipBig
1471 badMaskPlanes = self.config.badMaskPlanes[:]
1472 badMaskPlanes.append(
"CLIPPED")
1473 badPixelMask = afwImage.Mask.getPlaneBitMask(badMaskPlanes)
1474 return AssembleCoaddTask.run(self, skyInfo, tempExpRefList, imageScalerList, weightList,
1475 result.clipSpans, mask=badPixelMask)
1478 """Return an exposure that contains the difference between unclipped 1481 Generate a difference image between clipped and unclipped coadds. 1482 Compute the difference image by subtracting an outlier-clipped coadd 1483 from an outlier-unclipped coadd. Return the difference image. 1487 skyInfo : `lsst.pipe.base.Struct` 1488 Patch geometry information, from getSkyInfo 1489 tempExpRefList : `list` 1490 List of data reference to tempExp 1491 imageScalerList : `list` 1492 List of image scalers 1498 exp : `lsst.afw.image.Exposure` 1499 Difference image of unclipped and clipped coadd wrapped in an Exposure 1504 configIntersection = {k: getattr(self.config, k)
1505 for k, v
in self.config.toDict().
items()
if (k
in config.keys())}
1506 config.update(**configIntersection)
1509 config.statistic =
'MEAN' 1511 coaddMean = task.run(skyInfo, tempExpRefList, imageScalerList, weightList).coaddExposure
1513 config.statistic =
'MEANCLIP' 1515 coaddClip = task.run(skyInfo, tempExpRefList, imageScalerList, weightList).coaddExposure
1517 coaddDiff = coaddMean.getMaskedImage().
Factory(coaddMean.getMaskedImage())
1518 coaddDiff -= coaddClip.getMaskedImage()
1519 exp = afwImage.ExposureF(coaddDiff)
1520 exp.setPsf(coaddMean.getPsf())
1524 """Detect clipped regions on an exposure and set the mask on the 1525 individual tempExp masks. 1527 Detect footprints in the difference image after smoothing the 1528 difference image with a Gaussian kernal. Identify footprints that 1529 overlap with one or two input ``coaddTempExps`` by comparing the 1530 computed overlap fraction to thresholds set in the config. A different 1531 threshold is applied depending on the number of overlapping visits 1532 (restricted to one or two). If the overlap exceeds the thresholds, 1533 the footprint is considered "CLIPPED" and is marked as such on the 1534 coaddTempExp. Return a struct with the clipped footprints, the indices 1535 of the ``coaddTempExps`` that end up overlapping with the clipped 1536 footprints, and a list of new masks for the ``coaddTempExps``. 1540 exp : `lsst.afw.image.Exposure` 1541 Exposure to run detection on. 1542 tempExpRefList : `list` 1543 List of data reference to tempExp. 1547 result : `lsst.pipe.base.Struct` 1548 Result struct with components: 1550 - ``clipFootprints``: list of clipped footprints. 1551 - ``clipIndices``: indices for each ``clippedFootprint`` in 1553 - ``clipSpans``: List of dictionaries containing spanSet lists 1554 to clip. Each element contains the new maskplane name 1555 ("CLIPPED") as the key and list of ``SpanSets`` as the value. 1556 - ``detectionFootprints``: List of DETECTED/DETECTED_NEGATIVE plane 1557 compressed into footprints. 1559 mask = exp.getMaskedImage().getMask()
1560 maskDetValue = mask.getPlaneBitMask(
"DETECTED") | mask.getPlaneBitMask(
"DETECTED_NEGATIVE")
1561 fpSet = self.clipDetection.detectFootprints(exp, doSmooth=
True, clearMask=
True)
1563 fpSet.positive.merge(fpSet.negative)
1564 footprints = fpSet.positive
1565 self.log.
info(
'Found %d potential clipped objects', len(footprints.getFootprints()))
1570 artifactSpanSets = [{
'CLIPPED':
list()}
for _
in tempExpRefList]
1573 visitDetectionFootprints = []
1575 dims = [len(tempExpRefList), len(footprints.getFootprints())]
1576 overlapDetArr = numpy.zeros(dims, dtype=numpy.uint16)
1577 ignoreArr = numpy.zeros(dims, dtype=numpy.uint16)
1580 for i, warpRef
in enumerate(tempExpRefList):
1582 immediate=
True).getMaskedImage().getMask()
1583 maskVisitDet = tmpExpMask.Factory(tmpExpMask, tmpExpMask.getBBox(afwImage.PARENT),
1584 afwImage.PARENT,
True)
1585 maskVisitDet &= maskDetValue
1587 visitDetectionFootprints.append(visitFootprints)
1589 for j, footprint
in enumerate(footprints.getFootprints()):
1594 for j, footprint
in enumerate(footprints.getFootprints()):
1595 nPixel = footprint.getArea()
1598 for i
in range(len(tempExpRefList)):
1599 ignore = ignoreArr[i, j]
1600 overlapDet = overlapDetArr[i, j]
1601 totPixel = nPixel - ignore
1604 if ignore > overlapDet
or totPixel <= 0.5*nPixel
or overlapDet == 0:
1606 overlap.append(overlapDet/
float(totPixel))
1609 overlap = numpy.array(overlap)
1610 if not len(overlap):
1617 if len(overlap) == 1:
1618 if overlap[0] > self.config.minClipFootOverlapSingle:
1623 clipIndex = numpy.where(overlap > self.config.minClipFootOverlap)[0]
1624 if len(clipIndex) == 1:
1626 keepIndex = [clipIndex[0]]
1629 clipIndex = numpy.where(overlap > self.config.minClipFootOverlapDouble)[0]
1630 if len(clipIndex) == 2
and len(overlap) > 3:
1631 clipIndexComp = numpy.where(overlap <= self.config.minClipFootOverlapDouble)[0]
1632 if numpy.max(overlap[clipIndexComp]) <= self.config.maxClipFootOverlapDouble:
1634 keepIndex = clipIndex
1639 for index
in keepIndex:
1640 globalIndex = indexList[index]
1641 artifactSpanSets[globalIndex][
'CLIPPED'].
append(footprint.spans)
1643 clipIndices.append(numpy.array(indexList)[keepIndex])
1644 clipFootprints.append(footprint)
1646 return pipeBase.Struct(clipFootprints=clipFootprints, clipIndices=clipIndices,
1647 clipSpans=artifactSpanSets, detectionFootprints=visitDetectionFootprints)
1649 def detectClipBig(self, clipList, clipFootprints, clipIndices, detectionFootprints,
1650 maskClipValue, maskDetValue, coaddBBox):
1651 """Return individual warp footprints for large artifacts and append 1652 them to ``clipList`` in place. 1654 Identify big footprints composed of many sources in the coadd 1655 difference that may have originated in a large diffuse source in the 1656 coadd. We do this by indentifying all clipped footprints that overlap 1657 significantly with each source in all the coaddTempExps. 1662 List of alt mask SpanSets with clipping information. Modified. 1663 clipFootprints : `list` 1664 List of clipped footprints. 1665 clipIndices : `list` 1666 List of which entries in tempExpClipList each footprint belongs to. 1668 Mask value of clipped pixels. 1670 Mask value of detected pixels. 1671 coaddBBox : `lsst.afw.geom.Box` 1672 BBox of the coadd and warps. 1676 bigFootprintsCoadd : `list` 1677 List of big footprints 1679 bigFootprintsCoadd = []
1681 for index, (clippedSpans, visitFootprints)
in enumerate(zip(clipList, detectionFootprints)):
1682 maskVisitDet = afwImage.MaskX(coaddBBox, 0x0)
1683 for footprint
in visitFootprints.getFootprints():
1684 footprint.spans.setMask(maskVisitDet, maskDetValue)
1687 clippedFootprintsVisit = []
1688 for foot, clipIndex
in zip(clipFootprints, clipIndices):
1689 if index
not in clipIndex:
1691 clippedFootprintsVisit.append(foot)
1692 maskVisitClip = maskVisitDet.Factory(maskVisitDet.getBBox(afwImage.PARENT))
1693 afwDet.setMaskFromFootprintList(maskVisitClip, clippedFootprintsVisit, maskClipValue)
1695 bigFootprintsVisit = []
1696 for foot
in visitFootprints.getFootprints():
1697 if foot.getArea() < self.config.minBigOverlap:
1700 if nCount > self.config.minBigOverlap:
1701 bigFootprintsVisit.append(foot)
1702 bigFootprintsCoadd.append(foot)
1704 for footprint
in bigFootprintsVisit:
1705 clippedSpans[
"CLIPPED"].
append(footprint.spans)
1707 return bigFootprintsCoadd
1711 assembleStaticSkyModel = pexConfig.ConfigurableField(
1712 target=AssembleCoaddTask,
1713 doc=
"Task to assemble an artifact-free, PSF-matched Coadd to serve as a" 1714 " naive/first-iteration model of the static sky.",
1716 detect = pexConfig.ConfigurableField(
1717 target=SourceDetectionTask,
1718 doc=
"Detect outlier sources on difference between each psfMatched warp and static sky model" 1720 detectTemplate = pexConfig.ConfigurableField(
1721 target=SourceDetectionTask,
1722 doc=
"Detect sources on static sky model. Only used if doPreserveContainedBySource is True" 1724 maxNumEpochs = pexConfig.Field(
1725 doc=
"Charactistic maximum local number of epochs/visits in which an artifact candidate can appear " 1726 "and still be masked. The effective maxNumEpochs is a broken linear function of local " 1727 "number of epochs (N): min(maxFractionEpochsLow*N, maxNumEpochs + maxFractionEpochsHigh*N). " 1728 "For each footprint detected on the image difference between the psfMatched warp and static sky " 1729 "model, if a significant fraction of pixels (defined by spatialThreshold) are residuals in more " 1730 "than the computed effective maxNumEpochs, the artifact candidate is deemed persistant rather " 1731 "than transient and not masked.",
1735 maxFractionEpochsLow = pexConfig.RangeField(
1736 doc=
"Fraction of local number of epochs (N) to use as effective maxNumEpochs for low N. " 1737 "Effective maxNumEpochs = " 1738 "min(maxFractionEpochsLow * N, maxNumEpochs + maxFractionEpochsHigh * N)",
1743 maxFractionEpochsHigh = pexConfig.RangeField(
1744 doc=
"Fraction of local number of epochs (N) to use as effective maxNumEpochs for high N. " 1745 "Effective maxNumEpochs = " 1746 "min(maxFractionEpochsLow * N, maxNumEpochs + maxFractionEpochsHigh * N)",
1751 spatialThreshold = pexConfig.RangeField(
1752 doc=
"Unitless fraction of pixels defining how much of the outlier region has to meet the " 1753 "temporal criteria. If 0, clip all. If 1, clip none.",
1757 inclusiveMin=
True, inclusiveMax=
True 1759 doScaleWarpVariance = pexConfig.Field(
1760 doc=
"Rescale Warp variance plane using empirical noise?",
1764 scaleWarpVariance = pexConfig.ConfigurableField(
1765 target=ScaleVarianceTask,
1766 doc=
"Rescale variance on warps",
1768 doPreserveContainedBySource = pexConfig.Field(
1769 doc=
"Rescue artifacts from clipping that completely lie within a footprint detected" 1770 "on the PsfMatched Template Coadd. Replicates a behavior of SafeClip.",
1774 doPrefilterArtifacts = pexConfig.Field(
1775 doc=
"Ignore artifact candidates that are mostly covered by the bad pixel mask, " 1776 "because they will be excluded anyway. This prevents them from contributing " 1777 "to the outlier epoch count image and potentially being labeled as persistant." 1778 "'Mostly' is defined by the config 'prefilterArtifactsRatio'.",
1782 prefilterArtifactsMaskPlanes = pexConfig.ListField(
1783 doc=
"Prefilter artifact candidates that are mostly covered by these bad mask planes.",
1785 default=(
'NO_DATA',
'BAD',
'SAT',
'SUSPECT'),
1787 prefilterArtifactsRatio = pexConfig.Field(
1788 doc=
"Prefilter artifact candidates with less than this fraction overlapping good pixels",
1792 psfMatchedWarps = pipeBase.InputDatasetField(
1793 doc=(
"PSF-Matched Warps are required by CompareWarp regardless of the coadd type requested. " 1794 "Only PSF-Matched Warps make sense for image subtraction. " 1795 "Therefore, they must be in the InputDatasetField and made available to the task."),
1796 nameTemplate=
"{inputCoaddName}Coadd_psfMatchedWarp",
1797 storageClass=
"ExposureF",
1798 dimensions=(
"Tract",
"Patch",
"SkyMap",
"Visit"),
1803 AssembleCoaddConfig.setDefaults(self)
1819 self.
detect.doTempLocalBackground =
False 1820 self.
detect.reEstimateBackground =
False 1821 self.
detect.returnOriginalFootprints =
False 1822 self.
detect.thresholdPolarity =
"both" 1823 self.
detect.thresholdValue = 5
1824 self.
detect.nSigmaToGrow = 2
1825 self.
detect.minPixels = 4
1826 self.
detect.isotropicGrow =
True 1827 self.
detect.thresholdType =
"pixel_stdev" 1835 """Assemble a compareWarp coadded image from a set of warps 1836 by masking artifacts detected by comparing PSF-matched warps. 1838 In ``AssembleCoaddTask``, we compute the coadd as an clipped mean (i.e., 1839 we clip outliers). The problem with doing this is that when computing the 1840 coadd PSF at a given location, individual visit PSFs from visits with 1841 outlier pixels contribute to the coadd PSF and cannot be treated correctly. 1842 In this task, we correct for this behavior by creating a new badMaskPlane 1843 'CLIPPED' which marks pixels in the individual warps suspected to contain 1844 an artifact. We populate this plane on the input warps by comparing 1845 PSF-matched warps with a PSF-matched median coadd which serves as a 1846 model of the static sky. Any group of pixels that deviates from the 1847 PSF-matched template coadd by more than config.detect.threshold sigma, 1848 is an artifact candidate. The candidates are then filtered to remove 1849 variable sources and sources that are difficult to subtract such as 1850 bright stars. This filter is configured using the config parameters 1851 ``temporalThreshold`` and ``spatialThreshold``. The temporalThreshold is 1852 the maximum fraction of epochs that the deviation can appear in and still 1853 be considered an artifact. The spatialThreshold is the maximum fraction of 1854 pixels in the footprint of the deviation that appear in other epochs 1855 (where other epochs is defined by the temporalThreshold). If the deviant 1856 region meets this criteria of having a significant percentage of pixels 1857 that deviate in only a few epochs, these pixels have the 'CLIPPED' bit 1858 set in the mask. These regions will not contribute to the final coadd. 1859 Furthermore, any routine to determine the coadd PSF can now be cognizant 1860 of clipped regions. Note that the algorithm implemented by this task is 1861 preliminary and works correctly for HSC data. Parameter modifications and 1862 or considerable redesigning of the algorithm is likley required for other 1865 ``CompareWarpAssembleCoaddTask`` sub-classes 1866 ``AssembleCoaddTask`` and instantiates ``AssembleCoaddTask`` 1867 as a subtask to generate the TemplateCoadd (the model of the static sky). 1871 The `lsst.pipe.base.cmdLineTask.CmdLineTask` interface supports a 1872 flag ``-d`` to import ``debug.py`` from your ``PYTHONPATH``; see 1873 ``baseDebug`` for more about ``debug.py`` files. 1875 This task supports the following debug variables: 1878 If True then save the Epoch Count Image as a fits file in the `figPath` 1880 Path to save the debug fits images and figures 1882 For example, put something like: 1884 .. code-block:: python 1887 def DebugInfo(name): 1888 di = lsstDebug.getInfo(name) 1889 if name == "lsst.pipe.tasks.assembleCoadd": 1890 di.saveCountIm = True 1891 di.figPath = "/desired/path/to/debugging/output/images" 1893 lsstDebug.Info = DebugInfo 1895 into your ``debug.py`` file and run ``assemebleCoadd.py`` with the 1896 ``--debug`` flag. Some subtasks may have their own debug variables; 1897 see individual Task documentation. 1901 ``CompareWarpAssembleCoaddTask`` assembles a set of warped images into a 1902 coadded image. The ``CompareWarpAssembleCoaddTask`` is invoked by running 1903 ``assembleCoadd.py`` with the flag ``--compareWarpCoadd``. 1904 Usage of ``assembleCoadd.py`` expects a data reference to the tract patch 1905 and filter to be coadded (specified using 1906 '--id = [KEY=VALUE1[^VALUE2[^VALUE3...] [KEY=VALUE1[^VALUE2[^VALUE3...] ...]]') 1907 along with a list of coaddTempExps to attempt to coadd (specified using 1908 '--selectId [KEY=VALUE1[^VALUE2[^VALUE3...] [KEY=VALUE1[^VALUE2[^VALUE3...] ...]]'). 1909 Only the warps that cover the specified tract and patch will be coadded. 1910 A list of the available optional arguments can be obtained by calling 1911 ``assembleCoadd.py`` with the ``--help`` command line argument: 1913 .. code-block:: none 1915 assembleCoadd.py --help 1917 To demonstrate usage of the ``CompareWarpAssembleCoaddTask`` in the larger 1918 context of multi-band processing, we will generate the HSC-I & -R band 1919 oadds from HSC engineering test data provided in the ``ci_hsc`` package. 1920 To begin, assuming that the lsst stack has been already set up, we must 1921 set up the ``obs_subaru`` and ``ci_hsc`` packages. 1922 This defines the environment variable ``$CI_HSC_DIR`` and points at the 1923 location of the package. The raw HSC data live in the ``$CI_HSC_DIR/raw`` 1924 directory. To begin assembling the coadds, we must first 1927 process the individual ccds in $CI_HSC_RAW to produce calibrated exposures 1929 create a skymap that covers the area of the sky present in the raw exposures 1931 warp the individual calibrated exposures to the tangent plane of the coadd 1933 We can perform all of these steps by running 1935 .. code-block:: none 1937 $CI_HSC_DIR scons warp-903986 warp-904014 warp-903990 warp-904010 warp-903988 1939 This will produce warped ``coaddTempExps`` for each visit. To coadd the 1940 warped data, we call ``assembleCoadd.py`` as follows: 1942 .. code-block:: none 1944 assembleCoadd.py --compareWarpCoadd $CI_HSC_DIR/DATA --id patch=5,4 tract=0 filter=HSC-I \ 1945 --selectId visit=903986 ccd=16 --selectId visit=903986 ccd=22 --selectId visit=903986 ccd=23 \ 1946 --selectId visit=903986 ccd=100 --selectId visit=904014 ccd=1 --selectId visit=904014 ccd=6 \ 1947 --selectId visit=904014 ccd=12 --selectId visit=903990 ccd=18 --selectId visit=903990 ccd=25 \ 1948 --selectId visit=904010 ccd=4 --selectId visit=904010 ccd=10 --selectId visit=904010 ccd=100 \ 1949 --selectId visit=903988 ccd=16 --selectId visit=903988 ccd=17 --selectId visit=903988 ccd=23 \ 1950 --selectId visit=903988 ccd=24 1952 This will process the HSC-I band data. The results are written in 1953 ``$CI_HSC_DIR/DATA/deepCoadd-results/HSC-I``. 1955 ConfigClass = CompareWarpAssembleCoaddConfig
1956 _DefaultName =
"compareWarpAssembleCoadd" 1959 AssembleCoaddTask.__init__(self, *args, **kwargs)
1960 self.makeSubtask(
"assembleStaticSkyModel")
1961 detectionSchema = afwTable.SourceTable.makeMinimalSchema()
1962 self.makeSubtask(
"detect", schema=detectionSchema)
1963 if self.config.doPreserveContainedBySource:
1964 self.makeSubtask(
"detectTemplate", schema=afwTable.SourceTable.makeMinimalSchema())
1965 if self.config.doScaleWarpVariance:
1966 self.makeSubtask(
"scaleWarpVariance")
1969 """Make inputs specific to Subclass with Gen 3 API 1971 Calls Gen3 `adaptArgsAndRun` instead of the Gen2 specific `runDataRef` 1973 Duplicates interface of`adaptArgsAndRun` method. 1974 Available to be implemented by subclasses only if they need the 1975 coadd dataRef for performing preliminary processing before 1976 assembling the coadd. 1981 Keys are the names of the configs describing input dataset types. 1982 Values are input Python-domain data objects (or lists of objects) 1983 retrieved from data butler. 1984 inputDataIds : `dict` 1985 Keys are the names of the configs describing input dataset types. 1986 Values are DataIds (or lists of DataIds) that task consumes for 1987 corresponding dataset type. 1988 DataIds are guaranteed to match data objects in ``inputData``. 1989 outputDataIds : `dict` 1990 Keys are the names of the configs describing input dataset types. 1991 Values are DataIds (or lists of DataIds) that task is to produce 1992 for corresponding dataset type. 1993 butler : `lsst.daf.butler.Butler` 1994 Gen3 Butler object for fetching additional data products before 1999 result : `lsst.pipe.base.Struct` 2000 Result struct with components: 2002 - ``templateCoadd`` : coadded exposure (``lsst.afw.image.Exposure``) 2003 - ``nImage``: N Image (``lsst.afw.image.Image``) 2007 templateCoadd = self.assembleStaticSkyModel.
adaptArgsAndRun(inputData, inputDataIds,
2008 outputDataIds, butler)
2009 if templateCoadd
is None:
2012 return pipeBase.Struct(templateCoadd=templateCoadd.coaddExposure,
2013 nImage=templateCoadd.nImage)
2016 """Make inputs specific to Subclass. 2018 Generate a templateCoadd to use as a native model of static sky to 2019 subtract from warps. 2023 dataRef : `lsst.daf.persistence.butlerSubset.ButlerDataRef` 2024 Butler dataRef for supplementary data. 2025 selectDataList : `list` (optional) 2026 Optional List of data references to Calexps. 2027 warpRefList : `list` (optional) 2028 Optional List of data references to Warps. 2032 result : `lsst.pipe.base.Struct` 2033 Result struct with components: 2035 - ``templateCoadd``: coadded exposure (``lsst.afw.image.Exposure``) 2036 - ``nImage``: N Image (``lsst.afw.image.Image``) 2038 templateCoadd = self.assembleStaticSkyModel.
runDataRef(dataRef, selectDataList, warpRefList)
2039 if templateCoadd
is None:
2042 return pipeBase.Struct(templateCoadd=templateCoadd.coaddExposure,
2043 nImage=templateCoadd.nImage)
2045 def _noTemplateMessage(self, warpType):
2046 warpName = (warpType[0].upper() + warpType[1:])
2047 message =
"""No %(warpName)s warps were found to build the template coadd which is 2048 required to run CompareWarpAssembleCoaddTask. To continue assembling this type of coadd, 2049 first either rerun makeCoaddTempExp with config.make%(warpName)s=True or 2050 coaddDriver with config.makeCoadTempExp.make%(warpName)s=True, before assembleCoadd. 2052 Alternatively, to use another algorithm with existing warps, retarget the CoaddDriverConfig to 2053 another algorithm like: 2055 from lsst.pipe.tasks.assembleCoadd import SafeClipAssembleCoaddTask 2056 config.assemble.retarget(SafeClipAssembleCoaddTask) 2057 """ % {
"warpName": warpName}
2060 def run(self, skyInfo, tempExpRefList, imageScalerList, weightList,
2061 supplementaryData, *args, **kwargs):
2062 """Assemble the coadd. 2064 Find artifacts and apply them to the warps' masks creating a list of 2065 alternative masks with a new "CLIPPED" plane and updated "NO_DATA" 2066 plane. Then pass these alternative masks to the base class's `run` 2071 skyInfo : `lsst.pipe.base.Struct` 2072 Patch geometry information. 2073 tempExpRefList : `list` 2074 List of data references to warps. 2075 imageScalerList : `list` 2076 List of image scalers. 2079 supplementaryData : `lsst.pipe.base.Struct` 2080 This Struct must contain a ``templateCoadd`` that serves as the 2081 model of the static sky. 2085 result : `lsst.pipe.base.Struct` 2086 Result struct with components: 2088 - ``coaddExposure``: coadded exposure (``lsst.afw.image.Exposure``). 2089 - ``nImage``: exposure count image (``lsst.afw.image.Image``), if requested. 2091 templateCoadd = supplementaryData.templateCoadd
2092 spanSetMaskList = self.
findArtifacts(templateCoadd, tempExpRefList, imageScalerList)
2093 badMaskPlanes = self.config.badMaskPlanes[:]
2094 badMaskPlanes.append(
"CLIPPED")
2095 badPixelMask = afwImage.Mask.getPlaneBitMask(badMaskPlanes)
2097 result = AssembleCoaddTask.run(self, skyInfo, tempExpRefList, imageScalerList, weightList,
2098 spanSetMaskList, mask=badPixelMask)
2102 self.
applyAltEdgeMask(result.coaddExposure.maskedImage.mask, spanSetMaskList)
2106 """Propagate alt EDGE mask to SENSOR_EDGE AND INEXACT_PSF planes. 2110 mask : `lsst.afw.image.Mask` 2112 altMaskList : `list` 2113 List of Dicts containing ``spanSet`` lists. 2114 Each element contains the new mask plane name (e.g. "CLIPPED 2115 and/or "NO_DATA") as the key, and list of ``SpanSets`` to apply to 2118 maskValue = mask.getPlaneBitMask([
"SENSOR_EDGE",
"INEXACT_PSF"])
2119 for visitMask
in altMaskList:
2120 if "EDGE" in visitMask:
2121 for spanSet
in visitMask[
'EDGE']:
2122 spanSet.clippedTo(mask.getBBox()).setMask(mask, maskValue)
2127 Loop through warps twice. The first loop builds a map with the count 2128 of how many epochs each pixel deviates from the templateCoadd by more 2129 than ``config.chiThreshold`` sigma. The second loop takes each 2130 difference image and filters the artifacts detected in each using 2131 count map to filter out variable sources and sources that are 2132 difficult to subtract cleanly. 2136 templateCoadd : `lsst.afw.image.Exposure` 2137 Exposure to serve as model of static sky. 2138 tempExpRefList : `list` 2139 List of data references to warps. 2140 imageScalerList : `list` 2141 List of image scalers. 2146 List of dicts containing information about CLIPPED 2147 (i.e., artifacts), NO_DATA, and EDGE pixels. 2150 self.log.
debug(
"Generating Count Image, and mask lists.")
2151 coaddBBox = templateCoadd.getBBox()
2152 slateIm = afwImage.ImageU(coaddBBox)
2153 epochCountImage = afwImage.ImageU(coaddBBox)
2154 nImage = afwImage.ImageU(coaddBBox)
2155 spanSetArtifactList = []
2156 spanSetNoDataMaskList = []
2157 spanSetEdgeList = []
2161 templateCoadd.mask.clearAllMaskPlanes()
2163 if self.config.doPreserveContainedBySource:
2164 templateFootprints = self.detectTemplate.detectFootprints(templateCoadd)
2166 templateFootprints =
None 2168 for warpRef, imageScaler
in zip(tempExpRefList, imageScalerList):
2170 if warpDiffExp
is not None:
2172 nImage.array += (numpy.isfinite(warpDiffExp.image.array) *
2173 ((warpDiffExp.mask.array & badPixelMask) == 0)).astype(numpy.uint16)
2174 fpSet = self.detect.detectFootprints(warpDiffExp, doSmooth=
False, clearMask=
True)
2175 fpSet.positive.merge(fpSet.negative)
2176 footprints = fpSet.positive
2178 spanSetList = [footprint.spans
for footprint
in footprints.getFootprints()]
2181 if self.config.doPrefilterArtifacts:
2183 for spans
in spanSetList:
2184 spans.setImage(slateIm, 1, doClip=
True)
2185 epochCountImage += slateIm
2191 nans = numpy.where(numpy.isnan(warpDiffExp.maskedImage.image.array), 1, 0)
2193 nansMask.setXY0(warpDiffExp.getXY0())
2194 edgeMask = warpDiffExp.mask
2195 spanSetEdgeMask = afwGeom.SpanSet.fromMask(edgeMask,
2196 edgeMask.getPlaneBitMask(
"EDGE")).split()
2200 nansMask = afwImage.MaskX(coaddBBox, 1)
2202 spanSetEdgeMask = []
2204 spanSetNoDataMask = afwGeom.SpanSet.fromMask(nansMask).split()
2206 spanSetNoDataMaskList.append(spanSetNoDataMask)
2207 spanSetArtifactList.append(spanSetList)
2208 spanSetEdgeList.append(spanSetEdgeMask)
2212 epochCountImage.writeFits(path)
2214 for i, spanSetList
in enumerate(spanSetArtifactList):
2216 filteredSpanSetList = self.
filterArtifacts(spanSetList, epochCountImage, nImage,
2218 spanSetArtifactList[i] = filteredSpanSetList
2221 for artifacts, noData, edge
in zip(spanSetArtifactList, spanSetNoDataMaskList, spanSetEdgeList):
2222 altMasks.append({
'CLIPPED': artifacts,
2228 """Remove artifact candidates covered by bad mask plane. 2230 Any future editing of the candidate list that does not depend on 2231 temporal information should go in this method. 2235 spanSetList : `list` 2236 List of SpanSets representing artifact candidates. 2237 exp : `lsst.afw.image.Exposure` 2238 Exposure containing mask planes used to prefilter. 2242 returnSpanSetList : `list` 2243 List of SpanSets with artifacts. 2245 badPixelMask = exp.mask.getPlaneBitMask(self.config.prefilterArtifactsMaskPlanes)
2246 goodArr = (exp.mask.array & badPixelMask) == 0
2247 returnSpanSetList = []
2248 bbox = exp.getBBox()
2249 x0, y0 = exp.getXY0()
2250 for i, span
in enumerate(spanSetList):
2251 y, x = span.clippedTo(bbox).indices()
2252 yIndexLocal = numpy.array(y) - y0
2253 xIndexLocal = numpy.array(x) - x0
2254 goodRatio = numpy.count_nonzero(goodArr[yIndexLocal, xIndexLocal])/span.getArea()
2255 if goodRatio > self.config.prefilterArtifactsRatio:
2256 returnSpanSetList.append(span)
2257 return returnSpanSetList
2259 def filterArtifacts(self, spanSetList, epochCountImage, nImage, footprintsToExclude=None):
2260 """Filter artifact candidates. 2264 spanSetList : `list` 2265 List of SpanSets representing artifact candidates. 2266 epochCountImage : `lsst.afw.image.Image` 2267 Image of accumulated number of warpDiff detections. 2268 nImage : `lsst.afw.image.Image` 2269 Image of the accumulated number of total epochs contributing. 2273 maskSpanSetList : `list` 2274 List of SpanSets with artifacts. 2277 maskSpanSetList = []
2278 x0, y0 = epochCountImage.getXY0()
2279 for i, span
in enumerate(spanSetList):
2280 y, x = span.indices()
2281 yIdxLocal = [y1 - y0
for y1
in y]
2282 xIdxLocal = [x1 - x0
for x1
in x]
2283 outlierN = epochCountImage.array[yIdxLocal, xIdxLocal]
2284 totalN = nImage.array[yIdxLocal, xIdxLocal]
2287 effMaxNumEpochsHighN = (self.config.maxNumEpochs +
2288 self.config.maxFractionEpochsHigh*numpy.mean(totalN))
2289 effMaxNumEpochsLowN = self.config.maxFractionEpochsLow * numpy.mean(totalN)
2290 effectiveMaxNumEpochs =
int(
min(effMaxNumEpochsLowN, effMaxNumEpochsHighN))
2291 nPixelsBelowThreshold = numpy.count_nonzero((outlierN > 0) &
2292 (outlierN <= effectiveMaxNumEpochs))
2293 percentBelowThreshold = nPixelsBelowThreshold / len(outlierN)
2294 if percentBelowThreshold > self.config.spatialThreshold:
2295 maskSpanSetList.append(span)
2297 if self.config.doPreserveContainedBySource
and footprintsToExclude
is not None:
2299 filteredMaskSpanSetList = []
2300 for span
in maskSpanSetList:
2302 for footprint
in footprintsToExclude.positive.getFootprints():
2303 if footprint.spans.contains(span):
2307 filteredMaskSpanSetList.append(span)
2308 maskSpanSetList = filteredMaskSpanSetList
2310 return maskSpanSetList
2312 def _readAndComputeWarpDiff(self, warpRef, imageScaler, templateCoadd):
2313 """Fetch a warp from the butler and return a warpDiff. 2317 warpRef : `lsst.daf.persistence.butlerSubset.ButlerDataRef` 2318 Butler dataRef for the warp. 2319 imageScaler : `lsst.pipe.tasks.scaleZeroPoint.ImageScaler` 2320 An image scaler object. 2321 templateCoadd : `lsst.afw.image.Exposure` 2322 Exposure to be substracted from the scaled warp. 2326 warp : `lsst.afw.image.Exposure` 2327 Exposure of the image difference between the warp and template. 2332 if not warpRef.datasetExists(warpName):
2333 self.log.
warn(
"Could not find %s %s; skipping it", warpName, warpRef.dataId)
2335 warp = warpRef.get(warpName, immediate=
True)
2337 imageScaler.scaleMaskedImage(warp.getMaskedImage())
2338 mi = warp.getMaskedImage()
2339 if self.config.doScaleWarpVariance:
2341 self.scaleWarpVariance.
run(mi)
2342 except Exception
as exc:
2343 self.log.
warn(
"Unable to rescale variance of warp (%s); leaving it as-is" % (exc,))
2344 mi -= templateCoadd.getMaskedImage()
2347 def _dataRef2DebugPath(self, prefix, warpRef, coaddLevel=False):
2348 """Return a path to which to write debugging output. 2350 Creates a hyphen-delimited string of dataId values for simple filenames. 2355 Prefix for filename. 2356 warpRef : `lsst.daf.persistence.butlerSubset.ButlerDataRef` 2357 Butler dataRef to make the path from. 2358 coaddLevel : `bool`, optional. 2359 If True, include only coadd-level keys (e.g., 'tract', 'patch', 2360 'filter', but no 'visit'). 2365 Path for debugging output. 2370 keys = warpRef.dataId.keys()
2371 keyList = sorted(keys, reverse=
True)
2373 filename =
"%s-%s.fits" % (prefix,
'-'.join([
str(warpRef.dataId[k])
for k
in keyList]))
2374 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 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)
std::vector< SchemaItem< Flag > > * items
def __init__(self, args, kwargs)
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)