37 starSelectorRegistry, PsfAttributes, SingleGaussianPsf
38 from lsst.ip.diffim import ImagePsfMatchTask, DipoleMeasurementTask, DipoleAnalysis, \
39 SourceFlagChecker, KernelCandidateF, cast_KernelCandidateF, makeKernelBasisList, \
40 KernelCandidateQa, DiaCatalogSourceSelector, DiaCatalogSourceSelectorConfig
43 FwhmPerSigma = 2 * math.sqrt(2 * math.log(2))
47 """Config for ImageDifferenceTask
49 doAddCalexpBackground = pexConfig.Field(dtype=bool, default=
True,
50 doc=
"Add background to calexp before processing it. Useful as ipDiffim does background matching.")
51 doUseRegister = pexConfig.Field(dtype=bool, default=
True,
52 doc=
"Use image-to-image registration to align template with science image")
53 doDebugRegister = pexConfig.Field(dtype=bool, default=
False,
54 doc=
"Writing debugging data for doUseRegister")
55 doSelectSources = pexConfig.Field(dtype=bool, default=
True,
56 doc=
"Select stars to use for kernel fitting")
57 doSelectDcrCatalog = pexConfig.Field(dtype=bool, default=
False,
58 doc=
"Select stars of extreme color as part of the control sample")
59 doSelectVariableCatalog = pexConfig.Field(dtype=bool, default=
False,
60 doc=
"Select stars that are variable to be part of the control sample")
61 doSubtract = pexConfig.Field(dtype=bool, default=
True, doc=
"Compute subtracted exposure?")
62 doPreConvolve = pexConfig.Field(dtype=bool, default=
True,
63 doc=
"Convolve science image by its PSF before PSF-matching?")
64 useGaussianForPreConvolution = pexConfig.Field(dtype=bool, default=
True,
65 doc=
"Use a simple gaussian PSF model for pre-convolution (else use fit PSF)? "
66 "Ignored if doPreConvolve false.")
67 doDetection = pexConfig.Field(dtype=bool, default=
True, doc=
"Detect sources?")
68 doMerge = pexConfig.Field(dtype=bool, default=
True,
69 doc=
"Merge positive and negative diaSources with grow radius set by growFootprint")
70 doMatchSources = pexConfig.Field(dtype=bool, default=
True,
71 doc=
"Match diaSources with input calexp sources and ref catalog sources")
72 doMeasurement = pexConfig.Field(dtype=bool, default=
True, doc=
"Measure diaSources?")
73 doWriteSubtractedExp = pexConfig.Field(dtype=bool, default=
True, doc=
"Write difference exposure?")
74 doWriteMatchedExp = pexConfig.Field(dtype=bool, default=
False,
75 doc=
"Write warped and PSF-matched template coadd exposure?")
76 doWriteSources = pexConfig.Field(dtype=bool, default=
True, doc=
"Write sources?")
77 doAddMetrics = pexConfig.Field(dtype=bool, default=
True,
78 doc=
"Add columns to the source table to hold analysis metrics?")
80 coaddName = pexConfig.Field(
81 doc=
"coadd name: typically one of deep or goodSeeing",
85 convolveTemplate = pexConfig.Field(
86 doc=
"Which image gets convolved (default = template)",
90 astrometer = pexConfig.ConfigurableField(
91 target = measAstrom.AstrometryTask,
92 doc =
"astrometry task; used to match sources to reference objects, but not to fit a WCS",
94 sourceSelector = starSelectorRegistry.makeField(
"Source selection algorithm", default=
"diacatalog")
95 subtract = pexConfig.ConfigurableField(
96 target=ImagePsfMatchTask,
97 doc=
"Warp and PSF match template to exposure, then subtract",
99 detection = pexConfig.ConfigurableField(
100 target=SourceDetectionTask,
101 doc=
"Low-threshold detection for final measurement",
103 dipoleMeasurement = pexConfig.ConfigurableField(
104 target=DipoleMeasurementTask,
105 doc=
"Final source measurement on low-threshold detections; dipole fitting enabled.",
107 measurement = pexConfig.ConfigurableField(
108 target=SourceMeasurementTask,
109 doc=
"Final source measurement on low-threshold detections; dipole fitting NOT enabled",
111 controlStepSize = pexConfig.Field(
112 doc=
"What step size (every Nth one) to select a control sample from the kernelSources",
116 controlRandomSeed = pexConfig.Field(
117 doc=
"Random seed for shuffing the control sample",
121 register = pexConfig.ConfigurableField(
123 doc=
"Task to enable image-to-image image registration (warping)",
126 templateBorderSize = pexConfig.Field(dtype=int, default=10,
127 doc=
"Number of pixels to grow the requested template image to account for warping")
129 templateSipOrder = pexConfig.Field(dtype=int, default=2,
130 doc=
"Sip Order for fitting the Template Wcs (default is too high, overfitting)")
132 growFootprint = pexConfig.Field(dtype=int, default=2,
133 doc=
"Grow positive and negative footprints by this amount before merging")
135 diaSourceMatchRadius = pexConfig.Field(dtype=float, default=0.5,
136 doc=
"Match radius (in arcseconds) for DiaSource to Source association")
138 maxDiaSourcesToMeasure = pexConfig.Field(dtype=int, default=200,
139 doc=
"Do not measure more than this many sources with dipoleMeasurement, use measurement")
143 self.sourceSelector.name =
"diacatalog"
148 self.detection.thresholdPolarity =
"both"
149 self.detection.reEstimateBackground =
False
150 self.detection.thresholdType =
"pixel_stdev"
156 self.measurement.algorithms.names.add(
"flux.peakLikelihood")
157 self.dipoleMeasurement.algorithms.names.add(
"flux.peakLikelihood")
163 pexConfig.Config.validate(self)
165 raise ValueError(
"Cannot run source measurement without source detection.")
167 raise ValueError(
"Cannot run source merging without source detection.")
170 """Subtract an image from a template coadd and measure the result
172 ConfigClass = ImageDifferenceConfig
173 _DefaultName =
"imageDifference"
176 pipeBase.CmdLineTask.__init__(self, **kwargs)
177 self.makeSubtask(
"subtract")
179 if self.config.doUseRegister:
180 self.makeSubtask(
"register")
182 if self.config.doSelectSources:
185 self.
schema = afwTable.SourceTable.makeMinimalSchema()
187 if self.config.doDetection:
188 self.makeSubtask(
"detection", schema=self.
schema)
189 if self.config.doMeasurement:
190 self.makeSubtask(
"dipoleMeasurement", schema=self.
schema, algMetadata=self.
algMetadata)
191 self.makeSubtask(
"measurement", schema=afwTable.SourceTable.makeMinimalSchema(),
193 self.schema.addField(self.dipoleMeasurement._ClassificationFlag,
"F",
194 "probability of being a dipole")
196 if self.config.doMatchSources:
197 self.schema.addField(
"refMatchId",
"L",
"unique id of reference catalog match")
198 self.schema.addField(
"srcMatchId",
"L",
"unique id of source match")
202 """Subtract an image from a template coadd and measure the result
205 - warp template coadd to match WCS of image
206 - PSF match image to warped template
207 - subtract image from PSF-matched, warped template
208 - persist difference image
212 @param sensorRef: sensor-level butler data reference, used for the following data products:
218 - self.config.coaddName + "Coadd_skyMap"
219 - self.config.coaddName + "Coadd"
220 Input or output, depending on config:
221 - self.config.coaddName + "Diff_subtractedExp"
222 Output, depending on config:
223 - self.config.coaddName + "Diff_matchedExp"
224 - self.config.coaddName + "Diff_src"
226 @return pipe_base Struct containing these fields:
227 - subtractedExposure: exposure after subtracting template;
228 the unpersisted version if subtraction not run but detection run
229 None if neither subtraction nor detection run (i.e. nothing useful done)
230 - subtractRes: results of subtraction task; None if subtraction not run
231 - sources: detected and possibly measured sources; None if detection not run
233 self.log.info(
"Processing %s" % (sensorRef.dataId))
236 subtractedExposure =
None
240 controlSources =
None
246 expBits = sensorRef.get(
"ccdExposureId_bits")
247 expId = long(sensorRef.get(
"ccdExposureId"))
248 idFactory = afwTable.IdFactory.makeSource(expId, 64 - expBits)
251 exposure = sensorRef.get(
"calexp", immediate=
True)
252 if self.config.doAddCalexpBackground:
253 mi = exposure.getMaskedImage()
254 mi += sensorRef.get(
"calexpBackground").getImage()
255 if not exposure.hasPsf():
256 raise pipeBase.TaskError(
"Exposure has no psf")
257 sciencePsf = exposure.getPsf()
259 subtractedExposureName = self.config.coaddName +
"Diff_differenceExp"
260 templateExposure =
None
261 templateSources =
None
262 if self.config.doSubtract:
263 templateExposure, templateSources = self.
getTemplate(exposure, sensorRef)
268 scienceSigmaOrig = psfAttr.computeGaussianWidth(psfAttr.ADAPTIVE_MOMENT)
272 psfAttr = PsfAttributes(templateExposure.getPsf(),
afwGeom.Point2I(ctr))
273 templateSigma = psfAttr.computeGaussianWidth(psfAttr.ADAPTIVE_MOMENT)
279 if self.config.doPreConvolve:
282 srcMI = exposure.getMaskedImage()
283 destMI = srcMI.Factory(srcMI.getDimensions())
285 if self.config.useGaussianForPreConvolution:
287 kWidth, kHeight = sciencePsf.getLocalKernel().getDimensions()
288 preConvPsf = SingleGaussianPsf(kWidth, kHeight, scienceSigmaOrig)
293 exposure.setMaskedImage(destMI)
294 scienceSigmaPost = scienceSigmaOrig * math.sqrt(2)
296 scienceSigmaPost = scienceSigmaOrig
299 if self.config.doSelectSources:
300 if not sensorRef.datasetExists(
"src"):
301 self.log.warn(
"Src product does not exist; running detection, measurement, selection")
303 selectSources = self.subtract.getSelectSources(
305 sigma = scienceSigmaPost,
306 doSmooth =
not self.doPreConvolve,
307 idFactory = idFactory,
310 self.log.info(
"Source selection via src product")
312 selectSources = sensorRef.get(
"src")
316 referenceFwhmPix=scienceSigmaPost * FwhmPerSigma,
317 targetFwhmPix=templateSigma * FwhmPerSigma))
319 if self.config.doAddMetrics:
321 kcQa = KernelCandidateQa(nparam)
322 selectSources = kcQa.addToSchema(selectSources)
324 astromRet = self.astrometer.loadAndMatch(exposure=exposure, sourceCat=selectSources)
325 matches = astromRet.matches
326 kernelSources = self.sourceSelector.selectSources(exposure, selectSources, matches=matches)
327 random.shuffle(kernelSources, random.random)
328 controlSources = kernelSources[::self.config.controlStepSize]
329 kernelSources = [k
for i,k
in enumerate(kernelSources)
if i % self.config.controlStepSize]
331 if self.config.doSelectDcrCatalog:
332 redSelector = DiaCatalogSourceSelector(
333 DiaCatalogSourceSelectorConfig(grMin=self.sourceSelector.config.grMax, grMax=99.999))
334 redSources = redSelector.selectSources(exposure, selectSources, matches=matches)
335 controlSources.extend(redSources)
337 blueSelector = DiaCatalogSourceSelector(
338 DiaCatalogSourceSelectorConfig(grMin=-99.999, grMax=self.sourceSelector.config.grMin))
339 blueSources = blueSelector.selectSources(exposure, selectSources, matches=matches)
340 controlSources.extend(blueSources)
342 if self.config.doSelectVariableCatalog:
343 varSelector = DiaCatalogSourceSelector(
344 DiaCatalogSourceSelectorConfig(includeVariable=
True))
345 varSources = varSelector.selectSources(exposure, selectSources, matches=matches)
346 controlSources.extend(varSources)
348 self.log.info(
"Selected %d / %d sources for Psf matching (%d for control sample)"
349 % (len(kernelSources), len(selectSources), len(controlSources)))
351 if self.config.doUseRegister:
352 self.log.info(
"Registering images")
354 if templateSources
is None:
357 templateSources = self.subtract.getSelectSources(
366 wcsResults = self.
fitAstrometry(templateSources, templateExposure, selectSources)
367 warpedExp = self.register.warpExposure(templateExposure, wcsResults.wcs,
368 exposure.getWcs(), exposure.getBBox())
369 templateExposure = warpedExp
374 if self.config.doDebugRegister:
376 srcToMatch = {x.second.getId() : x.first
for x
in matches}
378 refCoordKey = wcsResults.matches[0].first.getTable().getCoordKey()
379 inCentroidKey = wcsResults.matches[0].second.getTable().getCentroidKey()
380 sids = [m.first.getId()
for m
in wcsResults.matches]
381 positions = [m.first.get(refCoordKey)
for m
in wcsResults.matches]
382 residuals = [m.first.get(refCoordKey).getOffsetFrom(wcsResults.wcs.pixelToSky(
383 m.second.get(inCentroidKey)))
for m
in wcsResults.matches]
384 allresids = dict(zip(sids, zip(positions, residuals)))
386 cresiduals = [m.first.get(refCoordKey).getTangentPlaneOffset(
387 wcsResults.wcs.pixelToSky(
388 m.second.get(inCentroidKey)))
for m
in wcsResults.matches]
389 colors = numpy.array([-2.5*numpy.log10(srcToMatch[x].get(
"g"))
390 + 2.5*numpy.log10(srcToMatch[x].get(
"r"))
391 for x
in sids
if x
in srcToMatch.keys()])
392 dlong = numpy.array([r[0].asArcseconds()
for s,r
in zip(sids, cresiduals)
393 if s
in srcToMatch.keys()])
394 dlat = numpy.array([r[1].asArcseconds()
for s,r
in zip(sids, cresiduals)
395 if s
in srcToMatch.keys()])
396 idx1 = numpy.where(colors<self.sourceSelector.config.grMin)
397 idx2 = numpy.where((colors>=self.sourceSelector.config.grMin)&
398 (colors<=self.sourceSelector.config.grMax))
399 idx3 = numpy.where(colors>self.sourceSelector.config.grMax)
400 rms1Long = IqrToSigma*(numpy.percentile(dlong[idx1],75)-numpy.percentile(dlong[idx1],25))
401 rms1Lat = IqrToSigma*(numpy.percentile(dlat[idx1],75)-numpy.percentile(dlat[idx1],25))
402 rms2Long = IqrToSigma*(numpy.percentile(dlong[idx2],75)-numpy.percentile(dlong[idx2],25))
403 rms2Lat = IqrToSigma*(numpy.percentile(dlat[idx2],75)-numpy.percentile(dlat[idx2],25))
404 rms3Long = IqrToSigma*(numpy.percentile(dlong[idx3],75)-numpy.percentile(dlong[idx3],25))
405 rms3Lat = IqrToSigma*(numpy.percentile(dlat[idx3],75)-numpy.percentile(dlat[idx3],25))
406 self.log.info(
"Blue star offsets'': %.3f %.3f, %.3f %.3f" % (numpy.median(dlong[idx1]),
408 numpy.median(dlat[idx1]),
410 self.log.info(
"Green star offsets'': %.3f %.3f, %.3f %.3f" % (numpy.median(dlong[idx2]),
412 numpy.median(dlat[idx2]),
414 self.log.info(
"Red star offsets'': %.3f %.3f, %.3f %.3f" % (numpy.median(dlong[idx3]),
416 numpy.median(dlat[idx3]),
419 self.metadata.add(
"RegisterBlueLongOffsetMedian", numpy.median(dlong[idx1]))
420 self.metadata.add(
"RegisterGreenLongOffsetMedian", numpy.median(dlong[idx2]))
421 self.metadata.add(
"RegisterRedLongOffsetMedian", numpy.median(dlong[idx3]))
422 self.metadata.add(
"RegisterBlueLongOffsetStd", rms1Long)
423 self.metadata.add(
"RegisterGreenLongOffsetStd", rms2Long)
424 self.metadata.add(
"RegisterRedLongOffsetStd", rms3Long)
426 self.metadata.add(
"RegisterBlueLatOffsetMedian", numpy.median(dlat[idx1]))
427 self.metadata.add(
"RegisterGreenLatOffsetMedian", numpy.median(dlat[idx2]))
428 self.metadata.add(
"RegisterRedLatOffsetMedian", numpy.median(dlat[idx3]))
429 self.metadata.add(
"RegisterBlueLatOffsetStd", rms1Lat)
430 self.metadata.add(
"RegisterGreenLatOffsetStd", rms2Lat)
431 self.metadata.add(
"RegisterRedLatOffsetStd", rms3Lat)
438 self.log.info(
"Subtracting images")
439 subtractRes = self.subtract.subtractExposures(
440 templateExposure=templateExposure,
441 scienceExposure=exposure,
442 scienceFwhmPix=scienceSigmaPost * FwhmPerSigma,
443 templateFwhmPix=templateSigma * FwhmPerSigma,
444 candidateList=kernelSources,
445 convolveTemplate=self.config.convolveTemplate,
446 doWarping=
not self.config.doUseRegister
448 subtractedExposure = subtractRes.subtractedExposure
450 if self.config.doWriteMatchedExp:
451 sensorRef.put(subtractRes.matchedExposure, self.config.coaddName +
"Diff_matchedExp")
453 if self.config.doDetection:
454 self.log.info(
"Running diaSource detection")
455 if subtractedExposure
is None:
456 subtractedExposure = sensorRef.get(subtractedExposureName)
459 if not subtractedExposure.hasPsf():
460 if self.config.convolveTemplate:
461 subtractedExposure.setPsf(exposure.getPsf())
463 if templateExposure
is None:
464 templateExposure, templateSources = self.
getTemplate(exposure, sensorRef)
465 subtractedExposure.setPsf(templateExposure.getPsf())
468 mask = subtractedExposure.getMaskedImage().getMask()
469 mask &= ~(mask.getPlaneBitMask(
"DETECTED") | mask.getPlaneBitMask(
"DETECTED_NEGATIVE"))
471 table = afwTable.SourceTable.make(self.
schema, idFactory)
473 results = self.detection.makeSourceCatalog(
475 exposure=subtractedExposure,
476 doSmooth=
not self.config.doPreConvolve
479 if self.config.doMerge:
480 fpSet = results.fpSets.positive
481 fpSet.merge(results.fpSets.negative, self.config.growFootprint,
482 self.config.growFootprint,
False)
484 fpSet.makeSources(diaSources)
485 self.log.info(
"Merging detections into %d sources" % (len(diaSources)))
487 diaSources = results.sources
489 if self.config.doMeasurement:
490 if len(diaSources) < self.config.maxDiaSourcesToMeasure:
491 self.log.info(
"Running diaSource dipole measurement")
492 self.dipoleMeasurement.run(subtractedExposure, diaSources)
494 self.log.info(
"Running diaSource measurement")
495 self.measurement.run(subtractedExposure, diaSources)
498 if self.config.doMatchSources:
499 if sensorRef.datasetExists(
"src"):
501 matchRadAsec = self.config.diaSourceMatchRadius
502 matchRadPixel = matchRadAsec / exposure.getWcs().pixelScale().asArcseconds()
504 srcMatches =
afwTable.matchXy(sensorRef.get(
"src"), diaSources, matchRadPixel,
True)
505 srcMatchDict = dict([(srcMatch.second.getId(), srcMatch.first.getId())
for
506 srcMatch
in srcMatches])
507 self.log.info(
"Matched %d / %d diaSources to sources" % (len(srcMatchDict),
510 self.log.warn(
"Src product does not exist; cannot match with diaSources")
514 refAstromConfig = measAstrom.AstrometryConfig()
515 refAstromConfig.matcher.maxMatchDistArcSec = matchRadAsec
516 refAstrometer = measAstrom.AstrometryTask(refAstromConfig)
517 astromRet = refAstrometer.run(exposure=exposure, sourceCat=diaSources)
518 refMatches = astromRet.matches
519 if refMatches
is None:
520 self.log.warn(
"No diaSource matches with reference catalog")
523 self.log.info(
"Matched %d / %d diaSources to reference catalog" % (len(refMatches),
525 refMatchDict = dict([(refMatch.second.getId(), refMatch.first.getId())
for \
526 refMatch
in refMatches])
529 for diaSource
in diaSources:
530 sid = diaSource.getId()
531 if srcMatchDict.has_key(sid):
532 diaSource.set(
"srcMatchId", srcMatchDict[sid])
533 if refMatchDict.has_key(sid):
534 diaSource.set(
"refMatchId", refMatchDict[sid])
536 if diaSources
is not None and self.config.doWriteSources:
537 sensorRef.put(diaSources, self.config.coaddName +
"Diff_diaSrc")
539 if self.config.doAddMetrics
and self.config.doSelectSources:
540 self.log.info(
"Evaluating metrics and control sample")
543 for cell
in subtractRes.kernelCellSet.getCellList():
544 for cand
in cell.begin(
False):
545 kernelCandList.append(cast_KernelCandidateF(cand))
548 basisList = afwMath.cast_LinearCombinationKernel(
549 kernelCandList[0].getKernel(KernelCandidateF.ORIG)).getKernelList()
552 diffimTools.sourceTableToCandidateList(controlSources,
553 subtractRes.warpedExposure, exposure,
554 self.config.subtract.kernel.active,
555 self.config.subtract.kernel.active.detectionConfig,
556 self.log, doBuild=
True, basisList=basisList)
558 kcQa.apply(kernelCandList, subtractRes.psfMatchingKernel, subtractRes.backgroundModel,
560 kcQa.apply(controlCandList, subtractRes.psfMatchingKernel, subtractRes.backgroundModel)
562 if self.config.doDetection:
563 kcQa.aggregate(selectSources, self.metadata, allresids, diaSources)
565 kcQa.aggregate(selectSources, self.metadata, allresids)
567 sensorRef.put(selectSources, self.config.coaddName +
"Diff_kernelSrc")
569 if self.config.doWriteSubtractedExp:
570 sensorRef.put(subtractedExposure, subtractedExposureName)
572 self.
runDebug(exposure, subtractRes, selectSources, kernelSources, diaSources)
573 return pipeBase.Struct(
574 subtractedExposure=subtractedExposure,
575 subtractRes=subtractRes,
580 """Fit the relative astrometry between templateSources and selectSources
582 @todo remove this method. It originally fit a new WCS to the template before calling register.run
583 because our TAN-SIP fitter behaved badly for points far from CRPIX, but that's been fixed.
584 It remains because a subtask overrides it.
586 results = self.register.run(templateSources, templateSources.getWcs(),
587 templateExposure.getBBox(), selectSources)
590 def runDebug(self, exposure, subtractRes, selectSources, kernelSources, diaSources):
599 if not maskTransparency:
601 ds9.setMaskTransparency(maskTransparency)
603 if display
and showSubtracted:
604 ds9.mtv(subtractRes.subtractedExposure, frame=lsstDebug.frame, title=
"Subtracted image")
605 mi = subtractRes.subtractedExposure.getMaskedImage()
606 x0, y0 = mi.getX0(), mi.getY0()
607 with ds9.Buffering():
609 x, y = s.getX() - x0, s.getY() - y0
610 ctype =
"red" if s.get(
"flags.negative")
else "yellow"
611 if (s.get(
"flags.pixel.interpolated.center")
or s.get(
"flags.pixel.saturated.center")
or
612 s.get(
"flags.pixel.cr.center")):
614 elif (s.get(
"flags.pixel.interpolated.any")
or s.get(
"flags.pixel.saturated.any")
or
615 s.get(
"flags.pixel.cr.any")):
619 ds9.dot(ptype, x, y, size=4, frame=lsstDebug.frame, ctype=ctype)
622 if display
and showPixelResiduals
and selectSources:
624 nonKernelSources = []
625 for source
in selectSources:
626 if not source
in kernelSources:
627 nonKernelSources.append(source)
629 diUtils.plotPixelResiduals(exposure,
630 subtractRes.warpedExposure,
631 subtractRes.subtractedExposure,
632 subtractRes.kernelCellSet,
633 subtractRes.psfMatchingKernel,
634 subtractRes.backgroundModel,
636 self.subtract.config.kernel.active.detectionConfig,
638 diUtils.plotPixelResiduals(exposure,
639 subtractRes.warpedExposure,
640 subtractRes.subtractedExposure,
641 subtractRes.kernelCellSet,
642 subtractRes.psfMatchingKernel,
643 subtractRes.backgroundModel,
645 self.subtract.config.kernel.active.detectionConfig,
647 if display
and showDiaSources:
649 flagChecker = SourceFlagChecker(diaSources)
650 isFlagged = [flagChecker(x)
for x
in diaSources]
651 isDipole = [x.get(
"classification.dipole")
for x
in diaSources]
652 diUtils.showDiaSources(diaSources, subtractRes.subtractedExposure, isFlagged, isDipole,
653 frame=lsstDebug.frame)
656 if display
and showDipoles:
657 DipoleAnalysis().displayDipoles(subtractRes.subtractedExposure, diaSources,
658 frame=lsstDebug.frame)
662 """Return a template coadd exposure that overlaps the exposure
664 @param[in] exposure: exposure
665 @param[in] sensorRef: a Butler data reference that can be used to obtain coadd data
667 @return coaddExposure: a template coadd exposure assembled out of patches
669 skyMap = sensorRef.get(datasetType=self.config.coaddName +
"Coadd_skyMap")
670 expWcs = exposure.getWcs()
672 expBoxD.grow(self.config.templateBorderSize)
673 ctrSkyPos = expWcs.pixelToSky(expBoxD.getCenter())
674 tractInfo = skyMap.findTract(ctrSkyPos)
675 self.log.info(
"Using skyMap tract %s" % (tractInfo.getId(),))
676 skyCorners = [expWcs.pixelToSky(pixPos)
for pixPos
in expBoxD.getCorners()]
677 patchList = tractInfo.findPatchList(skyCorners)
680 raise RuntimeError(
"No suitable tract found")
681 self.log.info(
"Assembling %s coadd patches" % (len(patchList),))
684 coaddWcs = tractInfo.getWcs()
686 for skyPos
in skyCorners:
687 coaddBBox.include(coaddWcs.skyToPixel(skyPos))
689 self.log.info(
"exposure dimensions=%s; coadd dimensions=%s" % \
690 (exposure.getDimensions(), coaddBBox.getDimensions()))
693 coaddExposure = afwImage.ExposureF(coaddBBox, coaddWcs)
694 coaddExposure.getMaskedImage().set(numpy.nan, afwImage.MaskU.getPlaneBitMask(
"NO_DATA"), numpy.nan)
699 for patchInfo
in patchList:
700 patchSubBBox = patchInfo.getOuterBBox()
701 patchSubBBox.clip(coaddBBox)
703 datasetType=self.config.coaddName +
"Coadd_sub",
705 tract=tractInfo.getId(),
706 patch=
"%s,%s" % (patchInfo.getIndex()[0], patchInfo.getIndex()[1]),
708 if patchSubBBox.isEmpty():
709 self.log.info(
"skip tract=%(tract)s; no overlapping pixels" % patchArgDict)
711 if not sensorRef.datasetExists(**patchArgDict):
712 self.log.warn(
"%(datasetType)s, tract=%(tract)s, patch=%(patch)s does not exist" \
717 self.log.info(
"Reading patch %s" % patchArgDict)
718 coaddPatch = sensorRef.get(patchArgDict, immediate=
True)
719 coaddView = afwImage.MaskedImageF(coaddExposure.getMaskedImage(), coaddPatch.getBBox())
720 coaddView <<= coaddPatch.getMaskedImage()
721 if coaddFilter
is None:
722 coaddFilter = coaddPatch.getFilter()
725 if coaddPsf
is None and coaddPatch.hasPsf():
726 coaddPsf = coaddPatch.getPsf()
728 if nPatchesFound == 0:
729 raise RuntimeError(
"No patches found!")
732 raise RuntimeError(
"No coadd Psf found!")
734 coaddExposure.setPsf(coaddPsf)
735 coaddExposure.setFilter(coaddFilter)
736 return coaddExposure, coaddSources
739 """Return the name of the config dataset
741 return "%sDiff_config" % (self.config.coaddName,)
744 """Return the name of the metadata dataset
746 return "%sDiff_metadata" % (self.config.coaddName,)
749 """Return a dict of empty catalogs for each catalog dataset produced by this task."""
752 return {self.config.coaddName +
"Diff_diaSrc": diaSrc}
756 """Create an argument parser
758 parser = pipeBase.ArgumentParser(name=cls._DefaultName)
759 parser.add_id_argument(
"--id",
"calexp", help=
"data ID, e.g. --id visit=12345 ccd=1,2")
763 winter2013TemplateId = pexConfig.Field(dtype=int, default=88868666,
764 doc=
"88868666 for sparse data; 22222200 (g) and 11111100 (i) for dense data")
765 winter2013WcsShift = pexConfig.Field(dtype=float, default=0.0,
766 doc=
"Shift stars going into RegisterTask by this amount")
767 winter2013WcsRms = pexConfig.Field(dtype=float, default=0.0,
768 doc=
"Perturb stars going into RegisterTask by this amount")
771 ConfigClass = Winter2013ImageDifferenceConfig
772 _DefaultName =
"winter2013ImageDifference"
775 ImageDifferenceTask.__init__(self, **kwargs)
778 """Return a template coadd exposure that overlaps the exposure
780 @param[in] exposure: exposure
781 @param[in] sensorRef: a Butler data reference that can be used to obtain coadd data
783 @return coaddExposure: a template coadd exposure assembled out of patches
786 self.log.warn(
"USING WINTER2013 : DEEP CALEXP AS TEMPLATE")
787 templateId = type(sensorRef.dataId)(sensorRef.dataId)
788 templateId[
"visit"] = self.config.winter2013TemplateId
789 template = sensorRef.getButler().get(datasetType=
"calexp", dataId=templateId)
790 if self.config.doAddCalexpBackground:
791 templateBg = sensorRef.getButler().get(datasetType=
"calexpBackground", dataId=templateId)
792 mi = template.getMaskedImage()
794 if not template.hasPsf():
795 raise pipeBase.TaskError(
"Template has no psf")
796 templateSources = sensorRef.getButler().get(datasetType=
"src", dataId=templateId)
797 return template, templateSources
801 """Fit the relative astrometry between templateSources and selectSources"""
802 if self.config.winter2013WcsShift > 0.0:
804 self.config.winter2013WcsShift)
805 cKey = templateSources[0].getTable().getCentroidKey()
806 for source
in templateSources:
807 centroid = source.get(cKey)
808 source.set(cKey, centroid+offset)
809 elif self.config.winter2013WcsRms > 0.0:
810 cKey = templateSources[0].getTable().getCentroidKey()
811 for source
in templateSources:
812 offset =
afwGeom.Extent2D(self.config.winter2013WcsRms*numpy.random.normal(),
813 self.config.winter2013WcsRms*numpy.random.normal())
814 centroid = source.get(cKey)
815 source.set(cKey, centroid+offset)
817 results = self.register.run(templateSources, templateExposure.getWcs(),
818 templateExposure.getBBox(), selectSources)
SourceMatchVector matchXy(SourceCatalog const &cat, double radius, bool symmetric=true)
Class for storing ordered metadata with comments.
Parameters to control convolution.
An integer coordinate rectangle.
Custom catalog class for record/table subclasses that are guaranteed to have an ID, and should generally be sorted by that ID.
void convolve(OutImageT &convolvedImage, InImageT const &inImage, KernelT const &kernel, bool doNormalize, bool doCopyEdge=false)
Old, deprecated version of convolve.
A floating-point coordinate rectangle geometry.