24 import lsst.pex.config 
as pexConfig
 
   33 from .coaddBase 
import CoaddBaseTask, makeSkyInfo
 
   34 from .warpAndPsfMatch 
import WarpAndPsfMatchTask
 
   35 from .coaddHelpers 
import groupPatchExposures, getGroupDataRef
 
   37 __all__ = [
"MakeCoaddTempExpTask", 
"MakeWarpTask", 
"MakeWarpConfig"]
 
   41     """Raised when data cannot be retrieved for an exposure. 
   42     When processing patches, sometimes one exposure is missing; this lets us 
   43     distinguish bewteen that case, and other errors. 
   49     """Config for MakeCoaddTempExpTask 
   51     warpAndPsfMatch = pexConfig.ConfigurableField(
 
   52         target=WarpAndPsfMatchTask,
 
   53         doc=
"Task to warp and PSF-match calexp",
 
   55     doWrite = pexConfig.Field(
 
   56         doc=
"persist <coaddName>Coadd_<warpType>Warp",
 
   60     bgSubtracted = pexConfig.Field(
 
   61         doc=
"Work with a background subtracted calexp?",
 
   65     coaddPsf = pexConfig.ConfigField(
 
   66         doc=
"Configuration for CoaddPsf",
 
   69     makeDirect = pexConfig.Field(
 
   70         doc=
"Make direct Warp/Coadds",
 
   74     makePsfMatched = pexConfig.Field(
 
   75         doc=
"Make Psf-Matched Warp/Coadd?",
 
   80     doWriteEmptyWarps = pexConfig.Field(
 
   83         doc=
"Write out warps even if they are empty" 
   86     hasFakes = pexConfig.Field(
 
   87         doc=
"Should be set to True if fake sources have been inserted into the input data.",
 
   91     doApplySkyCorr = pexConfig.Field(dtype=bool, default=
False, doc=
"Apply sky correction?")
 
   94         CoaddBaseTask.ConfigClass.validate(self)
 
   96             raise RuntimeError(
"At least one of config.makePsfMatched and config.makeDirect must be True")
 
   99             log.warn(
"Config doPsfMatch deprecated. Setting makePsfMatched=True and makeDirect=False")
 
  104         CoaddBaseTask.ConfigClass.setDefaults(self)
 
  105         self.
warpAndPsfMatch.psfMatch.kernel.active.kernelSize = self.matchingKernelSize
 
  116     r"""!Warp and optionally PSF-Match calexps onto an a common projection. 
  118     @anchor MakeCoaddTempExpTask_ 
  120     @section pipe_tasks_makeCoaddTempExp_Contents  Contents 
  122      - @ref pipe_tasks_makeCoaddTempExp_Purpose 
  123      - @ref pipe_tasks_makeCoaddTempExp_Initialize 
  124      - @ref pipe_tasks_makeCoaddTempExp_IO 
  125      - @ref pipe_tasks_makeCoaddTempExp_Config 
  126      - @ref pipe_tasks_makeCoaddTempExp_Debug 
  127      - @ref pipe_tasks_makeCoaddTempExp_Example 
  129     @section pipe_tasks_makeCoaddTempExp_Purpose  Description 
  131     Warp and optionally PSF-Match calexps onto a common projection, by 
  132     performing the following operations: 
  133     - Group calexps by visit/run 
  134     - For each visit, generate a Warp by calling method @ref makeTempExp. 
  135       makeTempExp loops over the visit's calexps calling @ref WarpAndPsfMatch 
  138     The result is a `directWarp` (and/or optionally a `psfMatchedWarp`). 
  140     @section pipe_tasks_makeCoaddTempExp_Initialize  Task Initialization 
  142     @copydoc \_\_init\_\_ 
  144     This task has one special keyword argument: passing reuse=True will cause 
  145     the task to skip the creation of warps that are already present in the 
  148     @section pipe_tasks_makeCoaddTempExp_IO  Invoking the Task 
  150     This task is primarily designed to be run from the command line. 
  152     The main method is `runDataRef`, which takes a single butler data reference for the patch(es) 
  157     WarpType identifies the types of convolutions applied to Warps (previously CoaddTempExps). 
  158     Only two types are available: direct (for regular Warps/Coadds) and psfMatched 
  159     (for Warps/Coadds with homogenized PSFs). We expect to add a third type, likelihood, 
  160     for generating likelihood Coadds with Warps that have been correlated with their own PSF. 
  162     @section pipe_tasks_makeCoaddTempExp_Config  Configuration parameters 
  164     See @ref MakeCoaddTempExpConfig and parameters inherited from 
  165     @link lsst.pipe.tasks.coaddBase.CoaddBaseConfig CoaddBaseConfig @endlink 
  167     @subsection pipe_tasks_MakeCoaddTempExp_psfMatching Guide to PSF-Matching Configs 
  169     To make `psfMatchedWarps`, select `config.makePsfMatched=True`. The subtask 
  170     @link lsst.ip.diffim.modelPsfMatch.ModelPsfMatchTask ModelPsfMatchTask @endlink 
  171     is responsible for the PSF-Matching, and its config is accessed via `config.warpAndPsfMatch.psfMatch`. 
  172     The optimal configuration depends on aspects of dataset: the pixel scale, average PSF FWHM and 
  173     dimensions of the PSF kernel. These configs include the requested model PSF, the matching kernel size, 
  174     padding of the science PSF thumbnail and spatial sampling frequency of the PSF. 
  176     *Config Guidelines*: The user must specify the size of the model PSF to which to match by setting 
  177     `config.modelPsf.defaultFwhm` in units of pixels. The appropriate values depends on science case. 
  178     In general, for a set of input images, this config should equal the FWHM of the visit 
  179     with the worst seeing. The smallest it should be set to is the median FWHM. The defaults 
  180     of the other config options offer a reasonable starting point. 
  181     The following list presents the most common problems that arise from a misconfigured 
  182     @link lsst.ip.diffim.modelPsfMatch.ModelPsfMatchTask ModelPsfMatchTask @endlink 
  183     and corresponding solutions. All assume the default Alard-Lupton kernel, with configs accessed via 
  184     ```config.warpAndPsfMatch.psfMatch.kernel['AL']```. Each item in the list is formatted as: 
  185     Problem: Explanation. *Solution* 
  187     *Troublshooting PSF-Matching Configuration:* 
  188     - Matched PSFs look boxy: The matching kernel is too small. _Increase the matching kernel size. 
  191             config.warpAndPsfMatch.psfMatch.kernel['AL'].kernelSize=27  # default 21 
  193         Note that increasing the kernel size also increases runtime. 
  194     - Matched PSFs look ugly (dipoles, quadropoles, donuts): unable to find good solution 
  195         for matching kernel. _Provide the matcher with more data by either increasing 
  196         the spatial sampling by decreasing the spatial cell size,_ 
  198             config.warpAndPsfMatch.psfMatch.kernel['AL'].sizeCellX = 64  # default 128 
  199             config.warpAndPsfMatch.psfMatch.kernel['AL'].sizeCellY = 64  # default 128 
  201         _or increasing the padding around the Science PSF, for example:_ 
  203             config.warpAndPsfMatch.psfMatch.autoPadPsfTo=1.6  # default 1.4 
  205         Increasing `autoPadPsfTo` increases the minimum ratio of input PSF dimensions to the 
  206         matching kernel dimensions, thus increasing the number of pixels available to fit 
  207         after convolving the PSF with the matching kernel. 
  208         Optionally, for debugging the effects of padding, the level of padding may be manually 
  209         controlled by setting turning off the automatic padding and setting the number 
  210         of pixels by which to pad the PSF: 
  212             config.warpAndPsfMatch.psfMatch.doAutoPadPsf = False  # default True 
  213             config.warpAndPsfMatch.psfMatch.padPsfBy = 6  # pixels. default 0 
  215     - Deconvolution: Matching a large PSF to a smaller PSF produces 
  216         a telltale noise pattern which looks like ripples or a brain. 
  217         _Increase the size of the requested model PSF. For example:_ 
  219             config.modelPsf.defaultFwhm = 11  # Gaussian sigma in units of pixels. 
  221     - High frequency (sometimes checkered) noise: The matching basis functions are too small. 
  222         _Increase the width of the Gaussian basis functions. For example:_ 
  224             config.warpAndPsfMatch.psfMatch.kernel['AL'].alardSigGauss=[1.5, 3.0, 6.0] 
  225             # from default [0.7, 1.5, 3.0] 
  228     @section pipe_tasks_makeCoaddTempExp_Debug  Debug variables 
  230     MakeCoaddTempExpTask has no debug output, but its subtasks do. 
  232     @section pipe_tasks_makeCoaddTempExp_Example   A complete example of using MakeCoaddTempExpTask 
  234     This example uses the package ci_hsc to show how MakeCoaddTempExp fits 
  235     into the larger Data Release Processing. 
  240         # if not built already: 
  241         python $(which scons)  # this will take a while 
  243     The following assumes that `processCcd.py` and `makeSkyMap.py` have previously been run 
  244     (e.g. by building `ci_hsc` above) to generate a repository of calexps and an 
  245     output respository with the desired SkyMap. The command, 
  247         makeCoaddTempExp.py $CI_HSC_DIR/DATA --rerun ci_hsc \ 
  248          --id patch=5,4 tract=0 filter=HSC-I \ 
  249          --selectId visit=903988 ccd=16 --selectId visit=903988 ccd=17 \ 
  250          --selectId visit=903988 ccd=23 --selectId visit=903988 ccd=24 \ 
  251          --config doApplyExternalPhotoCalib=False doApplyExternalSkyWcs=False \ 
  252          makePsfMatched=True modelPsf.defaultFwhm=11 
  254     writes a direct and PSF-Matched Warp to 
  255     - `$CI_HSC_DIR/DATA/rerun/ci_hsc/deepCoadd/HSC-I/0/5,4/warp-HSC-I-0-5,4-903988.fits` and 
  256     - `$CI_HSC_DIR/DATA/rerun/ci_hsc/deepCoadd/HSC-I/0/5,4/psfMatchedWarp-HSC-I-0-5,4-903988.fits` 
  259     @note PSF-Matching in this particular dataset would benefit from adding 
  260     `--configfile ./matchingConfig.py` to 
  261     the command line arguments where `matchingConfig.py` is defined by: 
  264         config.warpAndPsfMatch.psfMatch.kernel['AL'].kernelSize=27 
  265         config.warpAndPsfMatch.psfMatch.kernel['AL'].alardSigGauss=[1.5, 3.0, 6.0]" > matchingConfig.py 
  268     Add the option `--help` to see more options. 
  270     ConfigClass = MakeCoaddTempExpConfig
 
  271     _DefaultName = 
"makeCoaddTempExp" 
  274         CoaddBaseTask.__init__(self, **kwargs)
 
  276         self.makeSubtask(
"warpAndPsfMatch")
 
  277         if self.config.hasFakes:
 
  284         """!Produce <coaddName>Coadd_<warpType>Warp images by warping and optionally PSF-matching. 
  286         @param[in] patchRef: data reference for sky map patch. Must include keys "tract", "patch", 
  287             plus the camera-specific filter key (e.g. "filter" or "band") 
  288         @return: dataRefList: a list of data references for the new <coaddName>Coadd_directWarps 
  289             if direct or both warp types are requested and <coaddName>Coadd_psfMatchedWarps if only psfMatched 
  292         @warning: this task assumes that all exposures in a warp (coaddTempExp) have the same filter. 
  294         @warning: this task sets the PhotoCalib of the coaddTempExp to the PhotoCalib of the first calexp 
  295         with any good pixels in the patch. For a mosaic camera the resulting PhotoCalib should be ignored 
  296         (assembleCoadd should determine zeropoint scaling without referring to it). 
  301         if self.config.makePsfMatched 
and not self.config.makeDirect:
 
  306         calExpRefList = self.
selectExposures(patchRef, skyInfo, selectDataList=selectDataList)
 
  308         if len(calExpRefList) == 0:
 
  309             self.log.
warn(
"No exposures to coadd for patch %s", patchRef.dataId)
 
  311         self.log.
info(
"Selected %d calexps for patch %s", len(calExpRefList), patchRef.dataId)
 
  312         calExpRefList = [calExpRef 
for calExpRef 
in calExpRefList 
if calExpRef.datasetExists(self.
calexpType)]
 
  313         self.log.
info(
"Processing %d existing calexps for patch %s", len(calExpRefList), patchRef.dataId)
 
  317         self.log.
info(
"Processing %d warp exposures for patch %s", len(groupData.groups), patchRef.dataId)
 
  320         for i, (tempExpTuple, calexpRefList) 
in enumerate(groupData.groups.items()):
 
  322                                          tempExpTuple, groupData.keys)
 
  323             if self.
reuse and tempExpRef.datasetExists(datasetType=primaryWarpDataset, write=
True):
 
  324                 self.log.
info(
"Skipping makeCoaddTempExp for %s; output already exists.", tempExpRef.dataId)
 
  325                 dataRefList.append(tempExpRef)
 
  327             self.log.
info(
"Processing Warp %d/%d: id=%s", i, len(groupData.groups), tempExpRef.dataId)
 
  333                 visitId = int(tempExpRef.dataId[
"visit"])
 
  334             except (KeyError, ValueError):
 
  341             for calExpInd, calExpRef 
in enumerate(calexpRefList):
 
  342                 self.log.
info(
"Reading calexp %s of %s for Warp id=%s", calExpInd+1, len(calexpRefList),
 
  345                     ccdId = calExpRef.get(
"ccdExposureId", immediate=
True)
 
  352                     calExpRef = calExpRef.butlerSubset.butler.dataRef(self.
calexpType,
 
  353                                                                       dataId=calExpRef.dataId,
 
  354                                                                       tract=skyInfo.tractInfo.getId())
 
  356                 except Exception 
as e:
 
  357                     self.log.
warn(
"Calexp %s not found; skipping it: %s", calExpRef.dataId, e)
 
  360                 if self.config.doApplySkyCorr:
 
  363                 calExpList.append(calExp)
 
  364                 ccdIdList.append(ccdId)
 
  365                 dataIdList.append(calExpRef.dataId)
 
  367             exps = self.
run(calExpList, ccdIdList, skyInfo, visitId, dataIdList).exposures
 
  369             if any(exps.values()):
 
  370                 dataRefList.append(tempExpRef)
 
  372                 self.log.
warn(
"Warp %s could not be created", tempExpRef.dataId)
 
  374             if self.config.doWrite:
 
  375                 for (warpType, exposure) 
in exps.items():  
 
  376                     if exposure 
is not None:
 
  382     def run(self, calExpList, ccdIdList, skyInfo, visitId=0, dataIdList=None):
 
  383         """Create a Warp from inputs 
  385         We iterate over the multiple calexps in a single exposure to construct 
  386         the warp (previously called a coaddTempExp) of that exposure to the 
  387         supplied tract/patch. 
  389         Pixels that receive no pixels are set to NAN; this is not correct 
  390         (violates LSST algorithms group policy), but will be fixed up by 
  391         interpolating after the coaddition. 
  393         @param calexpRefList: List of data references for calexps that (may) 
  394             overlap the patch of interest 
  395         @param skyInfo: Struct from CoaddBaseTask.getSkyInfo() with geometric 
  396             information about the patch 
  397         @param visitId: integer identifier for visit, for the table that will 
  399         @return a pipeBase Struct containing: 
  400           - exposures: a dictionary containing the warps requested: 
  401                 "direct": direct warp if config.makeDirect 
  402                 "psfMatched": PSF-matched warp if config.makePsfMatched 
  406         totGoodPix = {warpType: 0 
for warpType 
in warpTypeList}
 
  407         didSetMetadata = {warpType: 
False for warpType 
in warpTypeList}
 
  409         inputRecorder = {warpType: self.inputRecorder.makeCoaddTempExpRecorder(visitId, len(calExpList))
 
  410                          for warpType 
in warpTypeList}
 
  412         modelPsf = self.config.modelPsf.apply() 
if self.config.makePsfMatched 
else None 
  413         if dataIdList 
is None:
 
  414             dataIdList = ccdIdList
 
  416         for calExpInd, (calExp, ccdId, dataId) 
in enumerate(zip(calExpList, ccdIdList, dataIdList)):
 
  417             self.log.
info(
"Processing calexp %d of %d for this Warp: id=%s",
 
  418                           calExpInd+1, len(calExpList), dataId)
 
  421                 warpedAndMatched = self.warpAndPsfMatch.
run(calExp, modelPsf=modelPsf,
 
  422                                                             wcs=skyInfo.wcs, maxBBox=skyInfo.bbox,
 
  423                                                             makeDirect=self.config.makeDirect,
 
  424                                                             makePsfMatched=self.config.makePsfMatched)
 
  425             except Exception 
as e:
 
  426                 self.log.
warn(
"WarpAndPsfMatch failed for calexp %s; skipping it: %s", dataId, e)
 
  429                 numGoodPix = {warpType: 0 
for warpType 
in warpTypeList}
 
  430                 for warpType 
in warpTypeList:
 
  431                     exposure = warpedAndMatched.getDict()[warpType]
 
  434                     coaddTempExp = coaddTempExps[warpType]
 
  435                     if didSetMetadata[warpType]:
 
  436                         mimg = exposure.getMaskedImage()
 
  437                         mimg *= (coaddTempExp.getPhotoCalib().getInstFluxAtZeroMagnitude()
 
  438                                  / exposure.getPhotoCalib().getInstFluxAtZeroMagnitude())
 
  441                         coaddTempExp.getMaskedImage(), exposure.getMaskedImage(), self.
getBadPixelMask())
 
  442                     totGoodPix[warpType] += numGoodPix[warpType]
 
  443                     self.log.
debug(
"Calexp %s has %d good pixels in this patch (%.1f%%) for %s",
 
  444                                    dataId, numGoodPix[warpType],
 
  445                                    100.0*numGoodPix[warpType]/skyInfo.bbox.getArea(), warpType)
 
  446                     if numGoodPix[warpType] > 0 
and not didSetMetadata[warpType]:
 
  447                         coaddTempExp.setPhotoCalib(exposure.getPhotoCalib())
 
  448                         coaddTempExp.setFilter(exposure.getFilter())
 
  449                         coaddTempExp.getInfo().setVisitInfo(exposure.getInfo().getVisitInfo())
 
  451                         coaddTempExp.setPsf(exposure.getPsf())
 
  452                         didSetMetadata[warpType] = 
True 
  455                     inputRecorder[warpType].addCalExp(calExp, ccdId, numGoodPix[warpType])
 
  457             except Exception 
as e:
 
  458                 self.log.
warn(
"Error processing calexp %s; skipping it: %s", dataId, e)
 
  461         for warpType 
in warpTypeList:
 
  462             self.log.
info(
"%sWarp has %d good pixels (%.1f%%)",
 
  463                           warpType, totGoodPix[warpType], 100.0*totGoodPix[warpType]/skyInfo.bbox.getArea())
 
  465             if totGoodPix[warpType] > 0 
and didSetMetadata[warpType]:
 
  466                 inputRecorder[warpType].finish(coaddTempExps[warpType], totGoodPix[warpType])
 
  467                 if warpType == 
"direct":
 
  468                     coaddTempExps[warpType].setPsf(
 
  469                         CoaddPsf(inputRecorder[warpType].coaddInputs.ccds, skyInfo.wcs,
 
  470                                  self.config.coaddPsf.makeControl()))
 
  472                 if not self.config.doWriteEmptyWarps:
 
  474                     coaddTempExps[warpType] = 
None 
  476         result = pipeBase.Struct(exposures=coaddTempExps)
 
  480         """Return one calibrated Exposure, possibly with an updated SkyWcs. 
  482         @param[in] dataRef        a sensor-level data reference 
  483         @param[in] bgSubtracted   return calexp with background subtracted? If False get the 
  484                                   calexp's background background model and add it to the calexp. 
  485         @return calibrated exposure 
  487         @raises MissingExposureError If data for the exposure is not available. 
  489         If config.doApplyExternalPhotoCalib is `True`, the photometric calibration 
  490         (`photoCalib`) is taken from `config.externalPhotoCalibName` via the 
  491         `name_photoCalib` dataset.  Otherwise, the photometric calibration is 
  492         retrieved from the processed exposure.  When 
  493         `config.doApplyExternalSkyWcs` is `True`, the astrometric calibration 
  494         is taken from `config.externalSkyWcsName` with the `name_wcs` dataset. 
  495         Otherwise, the astrometric calibration is taken from the processed 
  499             exposure = dataRef.get(self.
calexpType, immediate=
True)
 
  504             background = dataRef.get(
"calexpBackground", immediate=
True)
 
  505             mi = exposure.getMaskedImage()
 
  506             mi += background.getImage()
 
  509         if self.config.doApplyExternalPhotoCalib:
 
  510             source = f
"{self.config.externalPhotoCalibName}_photoCalib" 
  511             self.log.
debug(
"Applying external photoCalib to %s from %s", dataRef.dataId, source)
 
  512             photoCalib = dataRef.get(source)
 
  513             exposure.setPhotoCalib(photoCalib)
 
  515             photoCalib = exposure.getPhotoCalib()
 
  517         if self.config.doApplyExternalSkyWcs:
 
  518             source = f
"{self.config.externalSkyWcsName}_wcs" 
  519             self.log.
debug(
"Applying external skyWcs to %s from %s", dataRef.dataId, source)
 
  520             skyWcs = dataRef.get(source)
 
  521             exposure.setWcs(skyWcs)
 
  523         exposure.maskedImage = photoCalib.calibrateImage(exposure.maskedImage,
 
  524                                                          includeScaleUncertainty=self.config.includeCalibVar)
 
  525         exposure.maskedImage /= photoCalib.getCalibrationMean()
 
  531     def _prepareEmptyExposure(skyInfo):
 
  532         """Produce an empty exposure for a given patch""" 
  533         exp = afwImage.ExposureF(skyInfo.bbox, skyInfo.wcs)
 
  535                                  .getPlaneBitMask(
"NO_DATA"), numpy.inf)
 
  539         """Return list of requested warp types per the config. 
  542         if self.config.makeDirect:
 
  543             warpTypeList.append(
"direct")
 
  544         if self.config.makePsfMatched:
 
  545             warpTypeList.append(
"psfMatched")
 
  549         """Apply correction to the sky background level 
  551         Sky corrections can be generated with the 'skyCorrection.py' 
  552         executable in pipe_drivers. Because the sky model used by that 
  553         code extends over the entire focal plane, this can produce 
  554         better sky subtraction. 
  556         The calexp is updated in-place. 
  560         dataRef : `lsst.daf.persistence.ButlerDataRef` 
  561             Data reference for calexp. 
  562         calexp : `lsst.afw.image.Exposure` or `lsst.afw.image.MaskedImage` 
  565         bg = dataRef.get(
"skyCorr")
 
  566         self.log.
debug(
"Applying sky correction to %s", dataRef.dataId)
 
  568             calexp = calexp.getMaskedImage()
 
  569         calexp -= bg.getImage()
 
  573                           dimensions=(
"tract", 
"patch", 
"skymap", 
"instrument", 
"visit"),
 
  574                           defaultTemplates={
"coaddName": 
"deep"}):
 
  575     calExpList = cT.Input(
 
  576         doc=
"Input exposures to be resampled and optionally PSF-matched onto a SkyMap projection/patch",
 
  578         storageClass=
"ExposureF",
 
  579         dimensions=(
"instrument", 
"visit", 
"detector"),
 
  582     backgroundList = cT.Input(
 
  583         doc=
"Input backgrounds to be added back into the calexp if bgSubtracted=False",
 
  584         name=
"calexpBackground",
 
  585         storageClass=
"Background",
 
  586         dimensions=(
"instrument", 
"visit", 
"detector"),
 
  589     skyCorrList = cT.Input(
 
  590         doc=
"Input Sky Correction to be subtracted from the calexp if doApplySkyCorr=True",
 
  592         storageClass=
"Background",
 
  593         dimensions=(
"instrument", 
"visit", 
"detector"),
 
  597         doc=
"Input definition of geometry/bbox and projection/wcs for warped exposures",
 
  598         name=
"{coaddName}Coadd_skyMap",
 
  599         storageClass=
"SkyMap",
 
  600         dimensions=(
"skymap",),
 
  603         doc=(
"Output direct warped exposure (previously called CoaddTempExp), produced by resampling ",
 
  604              "calexps onto the skyMap patch geometry."),
 
  605         name=
"{coaddName}Coadd_directWarp",
 
  606         storageClass=
"ExposureF",
 
  607         dimensions=(
"tract", 
"patch", 
"skymap", 
"visit", 
"instrument"),
 
  609     psfMatched = cT.Output(
 
  610         doc=(
"Output PSF-Matched warped exposure (previously called CoaddTempExp), produced by resampling ",
 
  611              "calexps onto the skyMap patch geometry and PSF-matching to a model PSF."),
 
  612         name=
"{coaddName}Coadd_psfMatchedWarp",
 
  613         storageClass=
"ExposureF",
 
  614         dimensions=(
"tract", 
"patch", 
"skymap", 
"visit", 
"instrument"),
 
  617     def __init__(self, *, config=None):
 
  618         super().__init__(config=config)
 
  619         if config.bgSubtracted:
 
  620             self.inputs.remove(
"backgroundList")
 
  621         if not config.doApplySkyCorr:
 
  622             self.inputs.remove(
"skyCorrList")
 
  623         if not config.makeDirect:
 
  624             self.outputs.remove(
"direct")
 
  625         if not config.makePsfMatched:
 
  626             self.outputs.remove(
"psfMatched")
 
  630                      pipelineConnections=MakeWarpConnections):
 
  635         if self.doApplyExternalPhotoCalib:
 
  636             raise RuntimeError(
"Gen3 MakeWarpTask cannot apply external PhotoCalib results. " 
  637                                "Please set doApplyExternalPhotoCalib=False.")
 
  638         if self.doApplyExternalSkyWcs:
 
  639             raise RuntimeError(
"Gen3 MakeWarpTask cannot apply external SkyWcs results. " 
  640                                "Please set doApplyExternalSkyWcs=False.")
 
  644     """Warp and optionally PSF-Match calexps onto an a common projection 
  646     First Draft of a Gen3 compatible MakeWarpTask which 
  647     currently does not handle doApplyExternalPhotoCalib=True or 
  648     doApplyExternalSkyWcs=True. 
  650     ConfigClass = MakeWarpConfig
 
  651     _DefaultName = 
"makeWarp" 
  653     @utils.inheritDoc(pipeBase.PipelineTask)
 
  654     def runQuantum(self, butlerQC, inputRefs, outputRefs):
 
  658         Construct warps for requested warp type for single epoch 
  660         PipelineTask (Gen3) entry point to warp and optionally PSF-match 
  661         calexps. This method is analogous to `runDataRef`. 
  664         inputs = butlerQC.get(inputRefs)
 
  668         skyMap = inputs.pop(
"skyMap")
 
  669         quantumDataId = butlerQC.quantum.dataId
 
  670         skyInfo = 
makeSkyInfo(skyMap, tractId=quantumDataId[
'tract'], patchId=quantumDataId[
'patch'])
 
  673         dataIdList = [ref.dataId 
for ref 
in inputRefs.calExpList]
 
  676         ccdIdList = [dataId.pack(
"visit_detector") 
for dataId 
in dataIdList]
 
  679         visits = [dataId[
'visit'] 
for dataId 
in dataIdList]
 
  680         assert(
all(visits[0] == visit 
for visit 
in visits))
 
  683         self.prepareCalibratedExposures(**inputs)
 
  684         results = self.run(**inputs, visitId=visitId, ccdIdList=ccdIdList, dataIdList=dataIdList,
 
  686         if self.config.makeDirect:
 
  687             butlerQC.put(results.exposures[
"direct"], outputRefs.direct)
 
  688         if self.config.makePsfMatched:
 
  689             butlerQC.put(results.exposures[
"psfMatched"], outputRefs.psfMatched)
 
  691     def prepareCalibratedExposures(self, calExpList, backgroundList=None, skyCorrList=None):
 
  692         """Calibrate and add backgrounds to input calExpList in place 
  694         TODO DM-17062: apply jointcal/meas_mosaic here 
  698         calExpList : `list` of `lsst.afw.image.Exposure` 
  699             Sequence of calexps to be modified in place 
  700         backgroundList : `list` of `lsst.afw.math.backgroundList` 
  701             Sequence of backgrounds to be added back in if bgSubtracted=False 
  702         skyCorrList : `list` of `lsst.afw.math.backgroundList` 
  703             Sequence of background corrections to be subtracted if doApplySkyCorr=True 
  705         backgroundList = len(calExpList)*[
None] 
if backgroundList 
is None else backgroundList
 
  706         skyCorrList = len(calExpList)*[
None] 
if skyCorrList 
is None else skyCorrList
 
  707         for calexp, background, skyCorr 
in zip(calExpList, backgroundList, skyCorrList):
 
  708             mi = calexp.maskedImage
 
  709             if not self.config.bgSubtracted:
 
  710                 mi += background.getImage()
 
  711             if self.config.doApplySkyCorr:
 
  712                 mi -= skyCorr.getImage()