27 import lsst.pex.config 
as pexConfig
 
   38 from lsst.meas.algorithms import SourceDetectionTask, SingleGaussianPsf, ObjectSizeStarSelectorTask
 
   39 from lsst.ip.diffim import (DipoleAnalysis, SourceFlagChecker, KernelCandidateF, makeKernelBasisList,
 
   40                             KernelCandidateQa, DiaCatalogSourceSelectorTask, DiaCatalogSourceSelectorConfig,
 
   41                             GetCoaddAsTemplateTask, GetCalexpAsTemplateTask, DipoleFitTask,
 
   42                             DecorrelateALKernelSpatialTask, subtractAlgorithmRegistry)
 
   47 __all__ = [
"ImageDifferenceConfig", 
"ImageDifferenceTask"]
 
   48 FwhmPerSigma = 2*math.sqrt(2*math.log(2))
 
   53                                      dimensions=(
"instrument", 
"visit", 
"detector", 
"skymap"),
 
   54                                      defaultTemplates={
"coaddName": 
"deep",
 
   59     exposure = pipeBase.connectionTypes.Input(
 
   60         doc=
"Input science exposure to subtract from.",
 
   61         dimensions=(
"instrument", 
"visit", 
"detector"),
 
   62         storageClass=
"ExposureF",
 
   74     skyMap = pipeBase.connectionTypes.Input(
 
   75         doc=
"Input definition of geometry/bbox and projection/wcs for template exposures",
 
   76         name=
"{skyMapName}Coadd_skyMap",
 
   77         dimensions=(
"skymap", ),
 
   78         storageClass=
"SkyMap",
 
   80     coaddExposures = pipeBase.connectionTypes.Input(
 
   81         doc=
"Input template to match and subtract from the exposure",
 
   82         dimensions=(
"tract", 
"patch", 
"skymap", 
"abstract_filter"),
 
   83         storageClass=
"ExposureF",
 
   84         name=
"{fakesType}{coaddName}Coadd{warpTypeSuffix}",
 
   88     dcrCoadds = pipeBase.connectionTypes.Input(
 
   89         doc=
"Input DCR template to match and subtract from the exposure",
 
   90         name=
"{fakesType}dcrCoadd{warpTypeSuffix}",
 
   91         storageClass=
"ExposureF",
 
   92         dimensions=(
"tract", 
"patch", 
"skymap", 
"abstract_filter", 
"subfilter"),
 
   96     subtractedExposure = pipeBase.connectionTypes.Output(
 
   97         doc=
"Output difference image",
 
   98         dimensions=(
"instrument", 
"visit", 
"detector"),
 
   99         storageClass=
"ExposureF",
 
  100         name=
"{fakesType}{coaddName}Diff_differenceExp",
 
  102     diaSources = pipeBase.connectionTypes.Output(
 
  103         doc=
"Output detected diaSources on the difference image",
 
  104         dimensions=(
"instrument", 
"visit", 
"detector"),
 
  105         storageClass=
"SourceCatalog",
 
  106         name=
"{fakesType}{coaddName}Diff_diaSrc",
 
  109     def __init__(self, *, config=None):
 
  110         super().__init__(config=config)
 
  111         if config.coaddName == 
'dcr':
 
  112             self.inputs.remove(
"coaddExposures")
 
  114             self.inputs.remove(
"dcrCoadds")
 
  120 class ImageDifferenceConfig(pipeBase.PipelineTaskConfig,
 
  121                             pipelineConnections=ImageDifferenceTaskConnections):
 
  122     """Config for ImageDifferenceTask 
  124     doAddCalexpBackground = pexConfig.Field(dtype=bool, default=
False,
 
  125                                             doc=
"Add background to calexp before processing it.  " 
  126                                                 "Useful as ipDiffim does background matching.")
 
  127     doUseRegister = pexConfig.Field(dtype=bool, default=
True,
 
  128                                     doc=
"Use image-to-image registration to align template with " 
  130     doDebugRegister = pexConfig.Field(dtype=bool, default=
False,
 
  131                                       doc=
"Writing debugging data for doUseRegister")
 
  132     doSelectSources = pexConfig.Field(dtype=bool, default=
True,
 
  133                                       doc=
"Select stars to use for kernel fitting")
 
  134     doSelectDcrCatalog = pexConfig.Field(dtype=bool, default=
False,
 
  135                                          doc=
"Select stars of extreme color as part of the control sample")
 
  136     doSelectVariableCatalog = pexConfig.Field(dtype=bool, default=
False,
 
  137                                               doc=
"Select stars that are variable to be part " 
  138                                                   "of the control sample")
 
  139     doSubtract = pexConfig.Field(dtype=bool, default=
True, doc=
"Compute subtracted exposure?")
 
  140     doPreConvolve = pexConfig.Field(dtype=bool, default=
True,
 
  141                                     doc=
"Convolve science image by its PSF before PSF-matching?")
 
  142     doScaleTemplateVariance = pexConfig.Field(dtype=bool, default=
False,
 
  143                                               doc=
"Scale variance of the template before PSF matching")
 
  144     useGaussianForPreConvolution = pexConfig.Field(dtype=bool, default=
True,
 
  145                                                    doc=
"Use a simple gaussian PSF model for pre-convolution " 
  146                                                        "(else use fit PSF)? Ignored if doPreConvolve false.")
 
  147     doDetection = pexConfig.Field(dtype=bool, default=
True, doc=
"Detect sources?")
 
  148     doDecorrelation = pexConfig.Field(dtype=bool, default=
False,
 
  149                                       doc=
"Perform diffim decorrelation to undo pixel correlation due to A&L " 
  150                                           "kernel convolution? If True, also update the diffim PSF.")
 
  151     doMerge = pexConfig.Field(dtype=bool, default=
True,
 
  152                               doc=
"Merge positive and negative diaSources with grow radius " 
  153                                   "set by growFootprint")
 
  154     doMatchSources = pexConfig.Field(dtype=bool, default=
True,
 
  155                                      doc=
"Match diaSources with input calexp sources and ref catalog sources")
 
  156     doMeasurement = pexConfig.Field(dtype=bool, default=
True, doc=
"Measure diaSources?")
 
  157     doDipoleFitting = pexConfig.Field(dtype=bool, default=
True, doc=
"Measure dipoles using new algorithm?")
 
  158     doForcedMeasurement = pexConfig.Field(
 
  161         doc=
"Force photometer diaSource locations on PVI?")
 
  162     doWriteSubtractedExp = pexConfig.Field(dtype=bool, default=
True, doc=
"Write difference exposure?")
 
  163     doWriteMatchedExp = pexConfig.Field(dtype=bool, default=
False,
 
  164                                         doc=
"Write warped and PSF-matched template coadd exposure?")
 
  165     doWriteSources = pexConfig.Field(dtype=bool, default=
True, doc=
"Write sources?")
 
  166     doAddMetrics = pexConfig.Field(dtype=bool, default=
True,
 
  167                                    doc=
"Add columns to the source table to hold analysis metrics?")
 
  169     coaddName = pexConfig.Field(
 
  170         doc=
"coadd name: typically one of deep, goodSeeing, or dcr",
 
  174     convolveTemplate = pexConfig.Field(
 
  175         doc=
"Which image gets convolved (default = template)",
 
  179     refObjLoader = pexConfig.ConfigurableField(
 
  180         target=LoadIndexedReferenceObjectsTask,
 
  181         doc=
"reference object loader",
 
  183     astrometer = pexConfig.ConfigurableField(
 
  184         target=AstrometryTask,
 
  185         doc=
"astrometry task; used to match sources to reference objects, but not to fit a WCS",
 
  187     sourceSelector = pexConfig.ConfigurableField(
 
  188         target=ObjectSizeStarSelectorTask,
 
  189         doc=
"Source selection algorithm",
 
  191     subtract = subtractAlgorithmRegistry.makeField(
"Subtraction Algorithm", default=
"al")
 
  192     decorrelate = pexConfig.ConfigurableField(
 
  193         target=DecorrelateALKernelSpatialTask,
 
  194         doc=
"Decorrelate effects of A&L kernel convolution on image difference, only if doSubtract is True. " 
  195         "If this option is enabled, then detection.thresholdValue should be set to 5.0 (rather than the " 
  198     doSpatiallyVarying = pexConfig.Field(
 
  201         doc=
"If using Zogy or A&L decorrelation, perform these on a grid across the " 
  202         "image in order to allow for spatial variations" 
  204     detection = pexConfig.ConfigurableField(
 
  205         target=SourceDetectionTask,
 
  206         doc=
"Low-threshold detection for final measurement",
 
  208     measurement = pexConfig.ConfigurableField(
 
  209         target=DipoleFitTask,
 
  210         doc=
"Enable updated dipole fitting method",
 
  212     forcedMeasurement = pexConfig.ConfigurableField(
 
  213         target=ForcedMeasurementTask,
 
  214         doc=
"Subtask to force photometer PVI at diaSource location.",
 
  216     getTemplate = pexConfig.ConfigurableField(
 
  217         target=GetCoaddAsTemplateTask,
 
  218         doc=
"Subtask to retrieve template exposure and sources",
 
  220     scaleVariance = pexConfig.ConfigurableField(
 
  221         target=ScaleVarianceTask,
 
  222         doc=
"Subtask to rescale the variance of the template " 
  223             "to the statistically expected level" 
  225     controlStepSize = pexConfig.Field(
 
  226         doc=
"What step size (every Nth one) to select a control sample from the kernelSources",
 
  230     controlRandomSeed = pexConfig.Field(
 
  231         doc=
"Random seed for shuffing the control sample",
 
  235     register = pexConfig.ConfigurableField(
 
  237         doc=
"Task to enable image-to-image image registration (warping)",
 
  239     kernelSourcesFromRef = pexConfig.Field(
 
  240         doc=
"Select sources to measure kernel from reference catalog if True, template if false",
 
  244     templateSipOrder = pexConfig.Field(
 
  245         dtype=int, default=2,
 
  246         doc=
"Sip Order for fitting the Template Wcs (default is too high, overfitting)" 
  248     growFootprint = pexConfig.Field(
 
  249         dtype=int, default=2,
 
  250         doc=
"Grow positive and negative footprints by this amount before merging" 
  252     diaSourceMatchRadius = pexConfig.Field(
 
  253         dtype=float, default=0.5,
 
  254         doc=
"Match radius (in arcseconds) for DiaSource to Source association" 
  260         self.subtract[
'al'].kernel.name = 
"AL" 
  261         self.subtract[
'al'].kernel.active.fitForBackground = 
True 
  262         self.subtract[
'al'].kernel.active.spatialKernelOrder = 1
 
  263         self.subtract[
'al'].kernel.active.spatialBgOrder = 2
 
  264         self.doPreConvolve = 
False 
  265         self.doMatchSources = 
False 
  266         self.doAddMetrics = 
False 
  267         self.doUseRegister = 
False 
  270         self.detection.thresholdPolarity = 
"both" 
  271         self.detection.thresholdValue = 5.5
 
  272         self.detection.reEstimateBackground = 
False 
  273         self.detection.thresholdType = 
"pixel_stdev" 
  279         self.measurement.algorithms.names.add(
'base_PeakLikelihoodFlux')
 
  280         self.measurement.plugins.names |= [
'base_LocalPhotoCalib',
 
  283         self.forcedMeasurement.plugins = [
"base_TransformedCentroid", 
"base_PsfFlux"]
 
  284         self.forcedMeasurement.copyColumns = {
 
  285             "id": 
"objectId", 
"parent": 
"parentObjectId", 
"coord_ra": 
"coord_ra", 
"coord_dec": 
"coord_dec"}
 
  286         self.forcedMeasurement.slots.centroid = 
"base_TransformedCentroid" 
  287         self.forcedMeasurement.slots.shape = 
None 
  290         random.seed(self.controlRandomSeed)
 
  293         pexConfig.Config.validate(self)
 
  294         if self.doAddMetrics 
and not self.doSubtract:
 
  295             raise ValueError(
"Subtraction must be enabled for kernel metrics calculation.")
 
  296         if not self.doSubtract 
and not self.doDetection:
 
  297             raise ValueError(
"Either doSubtract or doDetection must be enabled.")
 
  298         if self.subtract.name == 
'zogy' and self.doAddMetrics:
 
  299             raise ValueError(
"Kernel metrics does not exist in zogy subtraction.")
 
  300         if self.doMeasurement 
and not self.doDetection:
 
  301             raise ValueError(
"Cannot run source measurement without source detection.")
 
  302         if self.doMerge 
and not self.doDetection:
 
  303             raise ValueError(
"Cannot run source merging without source detection.")
 
  304         if self.doUseRegister 
and not self.doSelectSources:
 
  305             raise ValueError(
"doUseRegister=True and doSelectSources=False. " 
  306                              "Cannot run RegisterTask without selecting sources.")
 
  307         if self.doPreConvolve 
and self.doDecorrelation 
and not self.convolveTemplate:
 
  308             raise ValueError(
"doPreConvolve=True and doDecorrelation=True and " 
  309                              "convolveTemplate=False is not supported.")
 
  310         if hasattr(self.getTemplate, 
"coaddName"):
 
  311             if self.getTemplate.coaddName != self.coaddName:
 
  312                 raise ValueError(
"Mis-matched coaddName and getTemplate.coaddName in the config.")
 
  315 class ImageDifferenceTaskRunner(pipeBase.ButlerInitializedTaskRunner):
 
  318     def getTargetList(parsedCmd, **kwargs):
 
  319         return pipeBase.TaskRunner.getTargetList(parsedCmd, templateIdList=parsedCmd.templateId.idList,
 
  323 class ImageDifferenceTask(pipeBase.CmdLineTask, pipeBase.PipelineTask):
 
  324     """Subtract an image from a template and measure the result 
  326     ConfigClass = ImageDifferenceConfig
 
  327     RunnerClass = ImageDifferenceTaskRunner
 
  328     _DefaultName = 
"imageDifference" 
  330     def __init__(self, butler=None, **kwargs):
 
  331         """!Construct an ImageDifference Task 
  333         @param[in] butler  Butler object to use in constructing reference object loaders 
  335         super().__init__(**kwargs)
 
  336         self.makeSubtask(
"getTemplate")
 
  338         self.makeSubtask(
"subtract")
 
  340         if self.config.subtract.name == 
'al' and self.config.doDecorrelation:
 
  341             self.makeSubtask(
"decorrelate")
 
  343         if self.config.doScaleTemplateVariance:
 
  344             self.makeSubtask(
"scaleVariance")
 
  346         if self.config.doUseRegister:
 
  347             self.makeSubtask(
"register")
 
  348         self.schema = afwTable.SourceTable.makeMinimalSchema()
 
  350         if self.config.doSelectSources:
 
  351             self.makeSubtask(
"sourceSelector")
 
  352             if self.config.kernelSourcesFromRef:
 
  353                 self.makeSubtask(
'refObjLoader', butler=butler)
 
  354                 self.makeSubtask(
"astrometer", refObjLoader=self.refObjLoader)
 
  357         if self.config.doDetection:
 
  358             self.makeSubtask(
"detection", schema=self.schema)
 
  359         if self.config.doMeasurement:
 
  360             self.makeSubtask(
"measurement", schema=self.schema,
 
  361                              algMetadata=self.algMetadata)
 
  362         if self.config.doForcedMeasurement:
 
  363             self.schema.addField(
 
  364                 "ip_diffim_forced_PsfFlux_instFlux", 
"D",
 
  365                 "Forced PSF flux measured on the direct image.")
 
  366             self.schema.addField(
 
  367                 "ip_diffim_forced_PsfFlux_instFluxErr", 
"D",
 
  368                 "Forced PSF flux error measured on the direct image.")
 
  369             self.schema.addField(
 
  370                 "ip_diffim_forced_PsfFlux_area", 
"F",
 
  371                 "Forced PSF flux effective area of PSF.",
 
  373             self.schema.addField(
 
  374                 "ip_diffim_forced_PsfFlux_flag", 
"Flag",
 
  375                 "Forced PSF flux general failure flag.")
 
  376             self.schema.addField(
 
  377                 "ip_diffim_forced_PsfFlux_flag_noGoodPixels", 
"Flag",
 
  378                 "Forced PSF flux not enough non-rejected pixels in data to attempt the fit.")
 
  379             self.schema.addField(
 
  380                 "ip_diffim_forced_PsfFlux_flag_edge", 
"Flag",
 
  381                 "Forced PSF flux object was too close to the edge of the image to use the full PSF model.")
 
  382             self.makeSubtask(
"forcedMeasurement", refSchema=self.schema)
 
  383         if self.config.doMatchSources:
 
  384             self.schema.addField(
"refMatchId", 
"L", 
"unique id of reference catalog match")
 
  385             self.schema.addField(
"srcMatchId", 
"L", 
"unique id of source match")
 
  388     def makeIdFactory(expId, expBits):
 
  389         """Create IdFactory instance for unique 64 bit diaSource id-s. 
  397             Number of used bits in ``expId``. 
  401         The diasource id-s consists of the ``expId`` stored fixed in the highest value 
  402         ``expBits`` of the 64-bit integer plus (bitwise or) a generated sequence number in the 
  403         low value end of the integer. 
  407         idFactory: `lsst.afw.table.IdFactory` 
  409         return afwTable.IdFactory.makeSource(expId, 64 - expBits)
 
  412     def runQuantum(self, butlerQC: pipeBase.ButlerQuantumContext,
 
  413                    inputRefs: pipeBase.InputQuantizedConnection,
 
  414                    outputRefs: pipeBase.OutputQuantizedConnection):
 
  415         inputs = butlerQC.get(inputRefs)
 
  416         self.log.
info(f
"Processing {butlerQC.quantum.dataId}")
 
  417         expId, expBits = butlerQC.quantum.dataId.pack(
"visit_detector",
 
  419         idFactory = self.makeIdFactory(expId=expId, expBits=expBits)
 
  420         if self.config.coaddName == 
'dcr':
 
  421             templateExposures = inputRefs.dcrCoadds
 
  423             templateExposures = inputRefs.coaddExposures
 
  424         templateStruct = self.getTemplate.runQuantum(
 
  425             inputs[
'exposure'], butlerQC, inputRefs.skyMap, templateExposures
 
  428         outputs = self.run(exposure=inputs[
'exposure'],
 
  429                            templateExposure=templateStruct.exposure,
 
  431         butlerQC.put(outputs, outputRefs)
 
  434     def runDataRef(self, sensorRef, templateIdList=None):
 
  435         """Subtract an image from a template coadd and measure the result. 
  437         Data I/O wrapper around `run` using the butler in Gen2. 
  441         sensorRef : `lsst.daf.persistence.ButlerDataRef` 
  442             Sensor-level butler data reference, used for the following data products: 
  449             - self.config.coaddName + "Coadd_skyMap" 
  450             - self.config.coaddName + "Coadd" 
  451             Input or output, depending on config: 
  452             - self.config.coaddName + "Diff_subtractedExp" 
  453             Output, depending on config: 
  454             - self.config.coaddName + "Diff_matchedExp" 
  455             - self.config.coaddName + "Diff_src" 
  459         results : `lsst.pipe.base.Struct` 
  460             Returns the Struct by `run`. 
  462         subtractedExposureName = self.config.coaddName + 
"Diff_differenceExp" 
  463         subtractedExposure = 
None 
  465         calexpBackgroundExposure = 
None 
  466         self.log.
info(
"Processing %s" % (sensorRef.dataId))
 
  471         idFactory = self.makeIdFactory(expId=int(sensorRef.get(
"ccdExposureId")),
 
  472                                        expBits=sensorRef.get(
"ccdExposureId_bits"))
 
  473         if self.config.doAddCalexpBackground:
 
  474             calexpBackgroundExposure = sensorRef.get(
"calexpBackground")
 
  477         exposure = sensorRef.get(
"calexp", immediate=
True)
 
  480         template = self.getTemplate.runDataRef(exposure, sensorRef, templateIdList=templateIdList)
 
  482         if sensorRef.datasetExists(
"src"):
 
  483             self.log.
info(
"Source selection via src product")
 
  485             selectSources = sensorRef.get(
"src")
 
  487         if not self.config.doSubtract 
and self.config.doDetection:
 
  489             subtractedExposure = sensorRef.get(subtractedExposureName)
 
  492         results = self.run(exposure=exposure,
 
  493                            selectSources=selectSources,
 
  494                            templateExposure=template.exposure,
 
  495                            templateSources=template.sources,
 
  497                            calexpBackgroundExposure=calexpBackgroundExposure,
 
  498                            subtractedExposure=subtractedExposure)
 
  500         if self.config.doWriteSources 
and results.diaSources 
is not None:
 
  501             sensorRef.put(results.diaSources, self.config.coaddName + 
"Diff_diaSrc")
 
  502         if self.config.doWriteMatchedExp:
 
  503             sensorRef.put(results.matchedExposure, self.config.coaddName + 
"Diff_matchedExp")
 
  504         if self.config.doAddMetrics 
and self.config.doSelectSources:
 
  505             sensorRef.put(results.selectSources, self.config.coaddName + 
"Diff_kernelSrc")
 
  506         if self.config.doWriteSubtractedExp:
 
  507             sensorRef.put(results.subtractedExposure, subtractedExposureName)
 
  510     def run(self, exposure=None, selectSources=None, templateExposure=None, templateSources=None,
 
  511             idFactory=None, calexpBackgroundExposure=None, subtractedExposure=None):
 
  512         """PSF matches, subtract two images and perform detection on the difference image. 
  516         exposure : `lsst.afw.image.ExposureF`, optional 
  517             The science exposure, the minuend in the image subtraction. 
  518             Can be None only if ``config.doSubtract==False``. 
  519         selectSources : `lsst.afw.table.SourceCatalog`, optional 
  520             Identified sources on the science exposure. This catalog is used to 
  521             select sources in order to perform the AL PSF matching on stamp images 
  522             around them. The selection steps depend on config options and whether 
  523             ``templateSources`` and ``matchingSources`` specified. 
  524         templateExposure : `lsst.afw.image.ExposureF`, optional 
  525             The template to be subtracted from ``exposure`` in the image subtraction. 
  526             The template exposure should cover the same sky area as the science exposure. 
  527             It is either a stich of patches of a coadd skymap image or a calexp 
  528             of the same pointing as the science exposure. Can be None only 
  529             if ``config.doSubtract==False`` and ``subtractedExposure`` is not None. 
  530         templateSources : `lsst.afw.table.SourceCatalog`, optional 
  531             Identified sources on the template exposure. 
  532         idFactory : `lsst.afw.table.IdFactory` 
  533             Generator object to assign ids to detected sources in the difference image. 
  534         calexpBackgroundExposure : `lsst.afw.image.ExposureF`, optional 
  535             Background exposure to be added back to the science exposure 
  536             if ``config.doAddCalexpBackground==True`` 
  537         subtractedExposure : `lsst.afw.image.ExposureF`, optional 
  538             If ``config.doSubtract==False`` and ``config.doDetection==True``, 
  539             performs the post subtraction source detection only on this exposure. 
  540             Otherwise should be None. 
  544         results : `lsst.pipe.base.Struct` 
  545             ``subtractedExposure`` : `lsst.afw.image.ExposureF` 
  547             ``matchedExposure`` : `lsst.afw.image.ExposureF` 
  548                 The matched PSF exposure. 
  549             ``subtractRes`` : `lsst.pipe.base.Struct` 
  550                 The returned result structure of the ImagePsfMatchTask subtask. 
  551             ``diaSources``  : `lsst.afw.table.SourceCatalog` 
  552                 The catalog of detected sources. 
  553             ``selectSources`` : `lsst.afw.table.SourceCatalog` 
  554                 The input source catalog with optionally added Qa information. 
  558         The following major steps are included: 
  560         - warp template coadd to match WCS of image 
  561         - PSF match image to warped template 
  562         - subtract image from PSF-matched, warped template 
  566         For details about the image subtraction configuration modes 
  567         see `lsst.ip.diffim`. 
  570         controlSources = 
None 
  574         if self.config.doAddCalexpBackground:
 
  575             mi = exposure.getMaskedImage()
 
  576             mi += calexpBackgroundExposure.getImage()
 
  578         if not exposure.hasPsf():
 
  579             raise pipeBase.TaskError(
"Exposure has no psf")
 
  580         sciencePsf = exposure.getPsf()
 
  582         if self.config.doSubtract:
 
  583             if self.config.doScaleTemplateVariance:
 
  584                 templateVarFactor = self.scaleVariance.
run(
 
  585                     templateExposure.getMaskedImage())
 
  586                 self.metadata.add(
"scaleTemplateVarianceFactor", templateVarFactor)
 
  588             if self.config.subtract.name == 
'zogy':
 
  589                 subtractRes = self.subtract.subtractExposures(templateExposure, exposure,
 
  591                                                               spatiallyVarying=self.config.doSpatiallyVarying,
 
  592                                                               doPreConvolve=self.config.doPreConvolve)
 
  593                 subtractedExposure = subtractRes.subtractedExposure
 
  595             elif self.config.subtract.name == 
'al':
 
  597                 scienceSigmaOrig = sciencePsf.computeShape().getDeterminantRadius()
 
  598                 templateSigma = templateExposure.getPsf().computeShape().getDeterminantRadius()
 
  606                 if self.config.doPreConvolve:
 
  609                     srcMI = exposure.getMaskedImage()
 
  610                     exposureOrig = exposure.clone()
 
  611                     destMI = srcMI.Factory(srcMI.getDimensions())
 
  613                     if self.config.useGaussianForPreConvolution:
 
  615                         kWidth, kHeight = sciencePsf.getLocalKernel().getDimensions()
 
  621                     exposure.setMaskedImage(destMI)
 
  622                     scienceSigmaPost = scienceSigmaOrig*math.sqrt(2)
 
  624                     scienceSigmaPost = scienceSigmaOrig
 
  625                     exposureOrig = exposure
 
  630                 if self.config.doSelectSources:
 
  631                     if selectSources 
is None:
 
  632                         self.log.
warn(
"Src product does not exist; running detection, measurement, selection")
 
  634                         selectSources = self.subtract.getSelectSources(
 
  636                             sigma=scienceSigmaPost,
 
  637                             doSmooth=
not self.config.doPreConvolve,
 
  641                     if self.config.doAddMetrics:
 
  645                                                          referenceFwhmPix=scienceSigmaPost*FwhmPerSigma,
 
  646                                                          targetFwhmPix=templateSigma*FwhmPerSigma))
 
  654                         selectSources = kcQa.addToSchema(selectSources)
 
  655                     if self.config.kernelSourcesFromRef:
 
  657                         astromRet = self.astrometer.loadAndMatch(exposure=exposure, sourceCat=selectSources)
 
  658                         matches = astromRet.matches
 
  659                     elif templateSources:
 
  662                         mc.findOnlyClosest = 
False 
  666                         raise RuntimeError(
"doSelectSources=True and kernelSourcesFromRef=False," 
  667                                            "but template sources not available. Cannot match science " 
  668                                            "sources with template sources. Run process* on data from " 
  669                                            "which templates are built.")
 
  671                     kernelSources = self.sourceSelector.
run(selectSources, exposure=exposure,
 
  672                                                             matches=matches).sourceCat
 
  673                     random.shuffle(kernelSources, random.random)
 
  674                     controlSources = kernelSources[::self.config.controlStepSize]
 
  675                     kernelSources = [k 
for i, k 
in enumerate(kernelSources)
 
  676                                      if i % self.config.controlStepSize]
 
  678                     if self.config.doSelectDcrCatalog:
 
  682                         redSources = redSelector.selectStars(exposure, selectSources, matches=matches).starCat
 
  683                         controlSources.extend(redSources)
 
  687                                                            grMax=self.sourceSelector.config.grMin))
 
  688                         blueSources = blueSelector.selectStars(exposure, selectSources,
 
  689                                                                matches=matches).starCat
 
  690                         controlSources.extend(blueSources)
 
  692                     if self.config.doSelectVariableCatalog:
 
  695                         varSources = varSelector.selectStars(exposure, selectSources, matches=matches).starCat
 
  696                         controlSources.extend(varSources)
 
  698                     self.log.
info(
"Selected %d / %d sources for Psf matching (%d for control sample)" 
  699                                   % (len(kernelSources), len(selectSources), len(controlSources)))
 
  703                 if self.config.doUseRegister:
 
  704                     self.log.
info(
"Registering images")
 
  706                     if templateSources 
is None:
 
  710                         templateSigma = templateExposure.getPsf().computeShape().getDeterminantRadius()
 
  711                         templateSources = self.subtract.getSelectSources(
 
  720                     wcsResults = self.fitAstrometry(templateSources, templateExposure, selectSources)
 
  721                     warpedExp = self.register.
warpExposure(templateExposure, wcsResults.wcs,
 
  722                                                            exposure.getWcs(), exposure.getBBox())
 
  723                     templateExposure = warpedExp
 
  728                     if self.config.doDebugRegister:
 
  730                         srcToMatch = {x.second.getId(): x.first 
for x 
in matches}
 
  732                         refCoordKey = wcsResults.matches[0].first.getTable().getCoordKey()
 
  733                         inCentroidKey = wcsResults.matches[0].second.getTable().getCentroidKey()
 
  734                         sids = [m.first.getId() 
for m 
in wcsResults.matches]
 
  735                         positions = [m.first.get(refCoordKey) 
for m 
in wcsResults.matches]
 
  736                         residuals = [m.first.get(refCoordKey).getOffsetFrom(wcsResults.wcs.pixelToSky(
 
  737                             m.second.get(inCentroidKey))) 
for m 
in wcsResults.matches]
 
  738                         allresids = dict(zip(sids, zip(positions, residuals)))
 
  740                         cresiduals = [m.first.get(refCoordKey).getTangentPlaneOffset(
 
  741                             wcsResults.wcs.pixelToSky(
 
  742                                 m.second.get(inCentroidKey))) 
for m 
in wcsResults.matches]
 
  743                         colors = numpy.array([-2.5*numpy.log10(srcToMatch[x].get(
"g"))
 
  744                                               + 2.5*numpy.log10(srcToMatch[x].get(
"r"))
 
  745                                               for x 
in sids 
if x 
in srcToMatch.keys()])
 
  746                         dlong = numpy.array([r[0].asArcseconds() 
for s, r 
in zip(sids, cresiduals)
 
  747                                              if s 
in srcToMatch.keys()])
 
  748                         dlat = numpy.array([r[1].asArcseconds() 
for s, r 
in zip(sids, cresiduals)
 
  749                                             if s 
in srcToMatch.keys()])
 
  750                         idx1 = numpy.where(colors < self.sourceSelector.config.grMin)
 
  751                         idx2 = numpy.where((colors >= self.sourceSelector.config.grMin)
 
  752                                            & (colors <= self.sourceSelector.config.grMax))
 
  753                         idx3 = numpy.where(colors > self.sourceSelector.config.grMax)
 
  754                         rms1Long = IqrToSigma*(
 
  755                             (numpy.percentile(dlong[idx1], 75) - numpy.percentile(dlong[idx1], 25)))
 
  756                         rms1Lat = IqrToSigma*(numpy.percentile(dlat[idx1], 75)
 
  757                                               - numpy.percentile(dlat[idx1], 25))
 
  758                         rms2Long = IqrToSigma*(
 
  759                             (numpy.percentile(dlong[idx2], 75) - numpy.percentile(dlong[idx2], 25)))
 
  760                         rms2Lat = IqrToSigma*(numpy.percentile(dlat[idx2], 75)
 
  761                                               - numpy.percentile(dlat[idx2], 25))
 
  762                         rms3Long = IqrToSigma*(
 
  763                             (numpy.percentile(dlong[idx3], 75) - numpy.percentile(dlong[idx3], 25)))
 
  764                         rms3Lat = IqrToSigma*(numpy.percentile(dlat[idx3], 75)
 
  765                                               - numpy.percentile(dlat[idx3], 25))
 
  766                         self.log.
info(
"Blue star offsets'': %.3f %.3f, %.3f %.3f" %
 
  767                                       (numpy.median(dlong[idx1]), rms1Long,
 
  768                                        numpy.median(dlat[idx1]), rms1Lat))
 
  769                         self.log.
info(
"Green star offsets'': %.3f %.3f, %.3f %.3f" %
 
  770                                       (numpy.median(dlong[idx2]), rms2Long,
 
  771                                        numpy.median(dlat[idx2]), rms2Lat))
 
  772                         self.log.
info(
"Red star offsets'': %.3f %.3f, %.3f %.3f" %
 
  773                                       (numpy.median(dlong[idx3]), rms3Long,
 
  774                                        numpy.median(dlat[idx3]), rms3Lat))
 
  776                         self.metadata.add(
"RegisterBlueLongOffsetMedian", numpy.median(dlong[idx1]))
 
  777                         self.metadata.add(
"RegisterGreenLongOffsetMedian", numpy.median(dlong[idx2]))
 
  778                         self.metadata.add(
"RegisterRedLongOffsetMedian", numpy.median(dlong[idx3]))
 
  779                         self.metadata.add(
"RegisterBlueLongOffsetStd", rms1Long)
 
  780                         self.metadata.add(
"RegisterGreenLongOffsetStd", rms2Long)
 
  781                         self.metadata.add(
"RegisterRedLongOffsetStd", rms3Long)
 
  783                         self.metadata.add(
"RegisterBlueLatOffsetMedian", numpy.median(dlat[idx1]))
 
  784                         self.metadata.add(
"RegisterGreenLatOffsetMedian", numpy.median(dlat[idx2]))
 
  785                         self.metadata.add(
"RegisterRedLatOffsetMedian", numpy.median(dlat[idx3]))
 
  786                         self.metadata.add(
"RegisterBlueLatOffsetStd", rms1Lat)
 
  787                         self.metadata.add(
"RegisterGreenLatOffsetStd", rms2Lat)
 
  788                         self.metadata.add(
"RegisterRedLatOffsetStd", rms3Lat)
 
  795                 self.log.
info(
"Subtracting images")
 
  796                 subtractRes = self.subtract.subtractExposures(
 
  797                     templateExposure=templateExposure,
 
  798                     scienceExposure=exposure,
 
  799                     candidateList=kernelSources,
 
  800                     convolveTemplate=self.config.convolveTemplate,
 
  801                     doWarping=
not self.config.doUseRegister
 
  803                 subtractedExposure = subtractRes.subtractedExposure
 
  805                 if self.config.doDetection:
 
  806                     self.log.
info(
"Computing diffim PSF")
 
  809                     if not subtractedExposure.hasPsf():
 
  810                         if self.config.convolveTemplate:
 
  811                             subtractedExposure.setPsf(exposure.getPsf())
 
  813                             subtractedExposure.setPsf(templateExposure.getPsf())
 
  820                 if self.config.doDecorrelation 
and self.config.doSubtract:
 
  822                     if preConvPsf 
is not None:
 
  823                         preConvKernel = preConvPsf.getLocalKernel()
 
  824                     if self.config.convolveTemplate:
 
  825                         self.log.
info(
"Decorrelation after template image convolution")
 
  826                         decorrResult = self.decorrelate.
run(exposureOrig, subtractRes.warpedExposure,
 
  828                                                             subtractRes.psfMatchingKernel,
 
  829                                                             spatiallyVarying=self.config.doSpatiallyVarying,
 
  830                                                             preConvKernel=preConvKernel)
 
  832                         self.log.
info(
"Decorrelation after science image convolution")
 
  833                         decorrResult = self.decorrelate.
run(subtractRes.warpedExposure, exposureOrig,
 
  835                                                             subtractRes.psfMatchingKernel,
 
  836                                                             spatiallyVarying=self.config.doSpatiallyVarying,
 
  837                                                             preConvKernel=preConvKernel)
 
  838                     subtractedExposure = decorrResult.correctedExposure
 
  842         if self.config.doDetection:
 
  843             self.log.
info(
"Running diaSource detection")
 
  845             mask = subtractedExposure.getMaskedImage().getMask()
 
  846             mask &= ~(mask.getPlaneBitMask(
"DETECTED") | mask.getPlaneBitMask(
"DETECTED_NEGATIVE"))
 
  848             table = afwTable.SourceTable.make(self.schema, idFactory)
 
  849             table.setMetadata(self.algMetadata)
 
  850             results = self.detection.
run(
 
  852                 exposure=subtractedExposure,
 
  853                 doSmooth=
not self.config.doPreConvolve
 
  856             if self.config.doMerge:
 
  857                 fpSet = results.fpSets.positive
 
  858                 fpSet.merge(results.fpSets.negative, self.config.growFootprint,
 
  859                             self.config.growFootprint, 
False)
 
  861                 fpSet.makeSources(diaSources)
 
  862                 self.log.
info(
"Merging detections into %d sources" % (len(diaSources)))
 
  864                 diaSources = results.sources
 
  866             if self.config.doMeasurement:
 
  867                 newDipoleFitting = self.config.doDipoleFitting
 
  868                 self.log.
info(
"Running diaSource measurement: newDipoleFitting=%r", newDipoleFitting)
 
  869                 if not newDipoleFitting:
 
  871                     self.measurement.
run(diaSources, subtractedExposure)
 
  874                     if self.config.doSubtract 
and 'matchedExposure' in subtractRes.getDict():
 
  875                         self.measurement.
run(diaSources, subtractedExposure, exposure,
 
  876                                              subtractRes.matchedExposure)
 
  878                         self.measurement.
run(diaSources, subtractedExposure, exposure)
 
  880             if self.config.doForcedMeasurement:
 
  883                 forcedSources = self.forcedMeasurement.generateMeasCat(
 
  884                     exposure, diaSources, subtractedExposure.getWcs())
 
  885                 self.forcedMeasurement.
run(forcedSources, exposure, diaSources, subtractedExposure.getWcs())
 
  887                 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_instFlux")[0],
 
  888                                   "ip_diffim_forced_PsfFlux_instFlux", 
True)
 
  889                 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_instFluxErr")[0],
 
  890                                   "ip_diffim_forced_PsfFlux_instFluxErr", 
True)
 
  891                 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_area")[0],
 
  892                                   "ip_diffim_forced_PsfFlux_area", 
True)
 
  893                 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_flag")[0],
 
  894                                   "ip_diffim_forced_PsfFlux_flag", 
True)
 
  895                 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_flag_noGoodPixels")[0],
 
  896                                   "ip_diffim_forced_PsfFlux_flag_noGoodPixels", 
True)
 
  897                 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_flag_edge")[0],
 
  898                                   "ip_diffim_forced_PsfFlux_flag_edge", 
True)
 
  899                 for diaSource, forcedSource 
in zip(diaSources, forcedSources):
 
  900                     diaSource.assign(forcedSource, mapper)
 
  903             if self.config.doMatchSources:
 
  904                 if selectSources 
is not None:
 
  906                     matchRadAsec = self.config.diaSourceMatchRadius
 
  907                     matchRadPixel = matchRadAsec/exposure.getWcs().getPixelScale().asArcseconds()
 
  910                     srcMatchDict = dict([(srcMatch.second.getId(), srcMatch.first.getId()) 
for 
  911                                          srcMatch 
in srcMatches])
 
  912                     self.log.
info(
"Matched %d / %d diaSources to sources" % (len(srcMatchDict),
 
  915                     self.log.
warn(
"Src product does not exist; cannot match with diaSources")
 
  920                 refAstromConfig.matcher.maxMatchDistArcSec = matchRadAsec
 
  922                 astromRet = refAstrometer.run(exposure=exposure, sourceCat=diaSources)
 
  923                 refMatches = astromRet.matches
 
  924                 if refMatches 
is None:
 
  925                     self.log.
warn(
"No diaSource matches with reference catalog")
 
  928                     self.log.
info(
"Matched %d / %d diaSources to reference catalog" % (len(refMatches),
 
  930                     refMatchDict = dict([(refMatch.second.getId(), refMatch.first.getId()) 
for 
  931                                          refMatch 
in refMatches])
 
  934                 for diaSource 
in diaSources:
 
  935                     sid = diaSource.getId()
 
  936                     if sid 
in srcMatchDict:
 
  937                         diaSource.set(
"srcMatchId", srcMatchDict[sid])
 
  938                     if sid 
in refMatchDict:
 
  939                         diaSource.set(
"refMatchId", refMatchDict[sid])
 
  941             if self.config.doAddMetrics 
and self.config.doSelectSources:
 
  942                 self.log.
info(
"Evaluating metrics and control sample")
 
  945                 for cell 
in subtractRes.kernelCellSet.getCellList():
 
  946                     for cand 
in cell.begin(
False):  
 
  947                         kernelCandList.append(cand)
 
  950                 basisList = kernelCandList[0].getKernel(KernelCandidateF.ORIG).getKernelList()
 
  951                 nparam = len(kernelCandList[0].getKernel(KernelCandidateF.ORIG).getKernelParameters())
 
  954                     diffimTools.sourceTableToCandidateList(controlSources,
 
  955                                                            subtractRes.warpedExposure, exposure,
 
  956                                                            self.config.subtract.kernel.active,
 
  957                                                            self.config.subtract.kernel.active.detectionConfig,
 
  958                                                            self.log, doBuild=
True, basisList=basisList))
 
  960                 KernelCandidateQa.apply(kernelCandList, subtractRes.psfMatchingKernel,
 
  961                                         subtractRes.backgroundModel, dof=nparam)
 
  962                 KernelCandidateQa.apply(controlCandList, subtractRes.psfMatchingKernel,
 
  963                                         subtractRes.backgroundModel)
 
  965                 if self.config.doDetection:
 
  966                     KernelCandidateQa.aggregate(selectSources, self.metadata, allresids, diaSources)
 
  968                     KernelCandidateQa.aggregate(selectSources, self.metadata, allresids)
 
  970         self.runDebug(exposure, subtractRes, selectSources, kernelSources, diaSources)
 
  971         return pipeBase.Struct(
 
  972             subtractedExposure=subtractedExposure,
 
  973             matchedExposure=subtractRes.matchedExposure,
 
  974             subtractRes=subtractRes,
 
  975             diaSources=diaSources,
 
  976             selectSources=selectSources
 
  979     def fitAstrometry(self, templateSources, templateExposure, selectSources):
 
  980         """Fit the relative astrometry between templateSources and selectSources 
  985         Remove this method. It originally fit a new WCS to the template before calling register.run 
  986         because our TAN-SIP fitter behaved badly for points far from CRPIX, but that's been fixed. 
  987         It remains because a subtask overrides it. 
  989         results = self.register.
run(templateSources, templateExposure.getWcs(),
 
  990                                     templateExposure.getBBox(), selectSources)
 
  993     def runDebug(self, exposure, subtractRes, selectSources, kernelSources, diaSources):
 
  994         """Make debug plots and displays. 
  998         Test and update for current debug display and slot names 
 1008             disp = afwDisplay.getDisplay(frame=lsstDebug.frame)
 
 1009             if not maskTransparency:
 
 1010                 maskTransparency = 0
 
 1011             disp.setMaskTransparency(maskTransparency)
 
 1013         if display 
and showSubtracted:
 
 1014             disp.mtv(subtractRes.subtractedExposure, title=
"Subtracted image")
 
 1015             mi = subtractRes.subtractedExposure.getMaskedImage()
 
 1016             x0, y0 = mi.getX0(), mi.getY0()
 
 1017             with disp.Buffering():
 
 1018                 for s 
in diaSources:
 
 1019                     x, y = s.getX() - x0, s.getY() - y0
 
 1020                     ctype = 
"red" if s.get(
"flags_negative") 
else "yellow" 
 1021                     if (s.get(
"base_PixelFlags_flag_interpolatedCenter")
 
 1022                             or s.get(
"base_PixelFlags_flag_saturatedCenter")
 
 1023                             or s.get(
"base_PixelFlags_flag_crCenter")):
 
 1025                     elif (s.get(
"base_PixelFlags_flag_interpolated")
 
 1026                           or s.get(
"base_PixelFlags_flag_saturated")
 
 1027                           or s.get(
"base_PixelFlags_flag_cr")):
 
 1031                     disp.dot(ptype, x, y, size=4, ctype=ctype)
 
 1032             lsstDebug.frame += 1
 
 1034         if display 
and showPixelResiduals 
and selectSources:
 
 1035             nonKernelSources = []
 
 1036             for source 
in selectSources:
 
 1037                 if source 
not in kernelSources:
 
 1038                     nonKernelSources.append(source)
 
 1040             diUtils.plotPixelResiduals(exposure,
 
 1041                                        subtractRes.warpedExposure,
 
 1042                                        subtractRes.subtractedExposure,
 
 1043                                        subtractRes.kernelCellSet,
 
 1044                                        subtractRes.psfMatchingKernel,
 
 1045                                        subtractRes.backgroundModel,
 
 1047                                        self.subtract.config.kernel.active.detectionConfig,
 
 1049             diUtils.plotPixelResiduals(exposure,
 
 1050                                        subtractRes.warpedExposure,
 
 1051                                        subtractRes.subtractedExposure,
 
 1052                                        subtractRes.kernelCellSet,
 
 1053                                        subtractRes.psfMatchingKernel,
 
 1054                                        subtractRes.backgroundModel,
 
 1056                                        self.subtract.config.kernel.active.detectionConfig,
 
 1058         if display 
and showDiaSources:
 
 1060             isFlagged = [flagChecker(x) 
for x 
in diaSources]
 
 1061             isDipole = [x.get(
"ip_diffim_ClassificationDipole_value") 
for x 
in diaSources]
 
 1062             diUtils.showDiaSources(diaSources, subtractRes.subtractedExposure, isFlagged, isDipole,
 
 1063                                    frame=lsstDebug.frame)
 
 1064             lsstDebug.frame += 1
 
 1066         if display 
and showDipoles:
 
 1067             DipoleAnalysis().displayDipoles(subtractRes.subtractedExposure, diaSources,
 
 1068                                             frame=lsstDebug.frame)
 
 1069             lsstDebug.frame += 1
 
 1071     def _getConfigName(self):
 
 1072         """Return the name of the config dataset 
 1074         return "%sDiff_config" % (self.config.coaddName,)
 
 1076     def _getMetadataName(self):
 
 1077         """Return the name of the metadata dataset 
 1079         return "%sDiff_metadata" % (self.config.coaddName,)
 
 1081     def getSchemaCatalogs(self):
 
 1082         """Return a dict of empty catalogs for each catalog dataset produced by this task.""" 
 1084         diaSrc.getTable().setMetadata(self.algMetadata)
 
 1085         return {self.config.coaddName + 
"Diff_diaSrc": diaSrc}
 
 1088     def _makeArgumentParser(cls):
 
 1089         """Create an argument parser 
 1091         parser = pipeBase.ArgumentParser(name=cls._DefaultName)
 
 1092         parser.add_id_argument(
"--id", 
"calexp", help=
"data ID, e.g. --id visit=12345 ccd=1,2")
 
 1093         parser.add_id_argument(
"--templateId", 
"calexp", doMakeDataRefList=
True,
 
 1094                                help=
"Template data ID in case of calexp template," 
 1095                                " e.g. --templateId visit=6789")
 
 1099 class Winter2013ImageDifferenceConfig(ImageDifferenceConfig):
 
 1100     winter2013WcsShift = pexConfig.Field(dtype=float, default=0.0,
 
 1101                                          doc=
"Shift stars going into RegisterTask by this amount")
 
 1102     winter2013WcsRms = pexConfig.Field(dtype=float, default=0.0,
 
 1103                                        doc=
"Perturb stars going into RegisterTask by this amount")
 
 1106         ImageDifferenceConfig.setDefaults(self)
 
 1107         self.getTemplate.retarget(GetCalexpAsTemplateTask)
 
 1110 class Winter2013ImageDifferenceTask(ImageDifferenceTask):
 
 1111     """!Image difference Task used in the Winter 2013 data challege. 
 1112     Enables testing the effects of registration shifts and scatter. 
 1114     For use with winter 2013 simulated images: 
 1115     Use --templateId visit=88868666 for sparse data 
 1116         --templateId visit=22222200 for dense data (g) 
 1117         --templateId visit=11111100 for dense data (i) 
 1119     ConfigClass = Winter2013ImageDifferenceConfig
 
 1120     _DefaultName = 
"winter2013ImageDifference" 
 1122     def __init__(self, **kwargs):
 
 1123         ImageDifferenceTask.__init__(self, **kwargs)
 
 1125     def fitAstrometry(self, templateSources, templateExposure, selectSources):
 
 1126         """Fit the relative astrometry between templateSources and selectSources""" 
 1127         if self.config.winter2013WcsShift > 0.0:
 
 1129                                    self.config.winter2013WcsShift)
 
 1130             cKey = templateSources[0].getTable().getCentroidKey()
 
 1131             for source 
in templateSources:
 
 1132                 centroid = source.get(cKey)
 
 1133                 source.set(cKey, centroid + offset)
 
 1134         elif self.config.winter2013WcsRms > 0.0:
 
 1135             cKey = templateSources[0].getTable().getCentroidKey()
 
 1136             for source 
in templateSources:
 
 1137                 offset = 
geom.Extent2D(self.config.winter2013WcsRms*numpy.random.normal(),
 
 1138                                        self.config.winter2013WcsRms*numpy.random.normal())
 
 1139                 centroid = source.get(cKey)
 
 1140                 source.set(cKey, centroid + offset)
 
 1142         results = self.register.
run(templateSources, templateExposure.getWcs(),
 
 1143                                     templateExposure.getBBox(), selectSources)