22 from __future__
import absolute_import, division, print_function
23 from builtins
import zip
28 import lsst.pex.config
as pexConfig
29 import lsst.pipe.base
as pipeBase
30 import lsst.daf.base
as dafBase
31 import lsst.afw.geom
as afwGeom
32 import lsst.afw.math
as afwMath
33 import lsst.afw.table
as afwTable
34 from lsst.meas.astrom
import AstrometryConfig, AstrometryTask
35 from lsst.meas.extensions.astrometryNet
import LoadAstrometryNetObjectsTask
37 from lsst.meas.algorithms
import SourceDetectionTask, SingleGaussianPsf, \
38 ObjectSizeStarSelectorTask
39 from lsst.ip.diffim
import ImagePsfMatchTask, DipoleAnalysis, \
40 SourceFlagChecker, KernelCandidateF, makeKernelBasisList, \
41 KernelCandidateQa, DiaCatalogSourceSelectorTask, DiaCatalogSourceSelectorConfig, \
42 GetCoaddAsTemplateTask, GetCalexpAsTemplateTask, DipoleFitTask, DecorrelateALKernelTask
43 import lsst.ip.diffim.diffimTools
as diffimTools
44 import lsst.ip.diffim.utils
as diUtils
46 FwhmPerSigma = 2 * math.sqrt(2 * math.log(2))
51 """Config for ImageDifferenceTask 53 doAddCalexpBackground = pexConfig.Field(dtype=bool, default=
True,
54 doc=
"Add background to calexp before processing it. " 55 "Useful as ipDiffim does background matching.")
56 doUseRegister = pexConfig.Field(dtype=bool, default=
True,
57 doc=
"Use image-to-image registration to align template with " 59 doDebugRegister = pexConfig.Field(dtype=bool, default=
False,
60 doc=
"Writing debugging data for doUseRegister")
61 doSelectSources = pexConfig.Field(dtype=bool, default=
True,
62 doc=
"Select stars to use for kernel fitting")
63 doSelectDcrCatalog = pexConfig.Field(dtype=bool, default=
False,
64 doc=
"Select stars of extreme color as part of the control sample")
65 doSelectVariableCatalog = pexConfig.Field(dtype=bool, default=
False,
66 doc=
"Select stars that are variable to be part " 67 "of the control sample")
68 doSubtract = pexConfig.Field(dtype=bool, default=
True, doc=
"Compute subtracted exposure?")
69 doPreConvolve = pexConfig.Field(dtype=bool, default=
True,
70 doc=
"Convolve science image by its PSF before PSF-matching?")
71 useGaussianForPreConvolution = pexConfig.Field(dtype=bool, default=
True,
72 doc=
"Use a simple gaussian PSF model for pre-convolution " 73 "(else use fit PSF)? Ignored if doPreConvolve false.")
74 doDetection = pexConfig.Field(dtype=bool, default=
True, doc=
"Detect sources?")
75 doDecorrelation = pexConfig.Field(dtype=bool, default=
False,
76 doc=
"Perform diffim decorrelation to undo pixel correlation due to A&L " 77 "kernel convolution? If True, also update the diffim PSF.")
78 doMerge = pexConfig.Field(dtype=bool, default=
True,
79 doc=
"Merge positive and negative diaSources with grow radius " 80 "set by growFootprint")
81 doMatchSources = pexConfig.Field(dtype=bool, default=
True,
82 doc=
"Match diaSources with input calexp sources and ref catalog sources")
83 doMeasurement = pexConfig.Field(dtype=bool, default=
True, doc=
"Measure diaSources?")
84 doDipoleFitting = pexConfig.Field(dtype=bool, default=
True, doc=
"Measure dipoles using new algorithm?")
85 doWriteSubtractedExp = pexConfig.Field(dtype=bool, default=
True, doc=
"Write difference exposure?")
86 doWriteMatchedExp = pexConfig.Field(dtype=bool, default=
False,
87 doc=
"Write warped and PSF-matched template coadd exposure?")
88 doWriteSources = pexConfig.Field(dtype=bool, default=
True, doc=
"Write sources?")
89 doAddMetrics = pexConfig.Field(dtype=bool, default=
True,
90 doc=
"Add columns to the source table to hold analysis metrics?")
92 coaddName = pexConfig.Field(
93 doc=
"coadd name: typically one of deep or goodSeeing",
97 convolveTemplate = pexConfig.Field(
98 doc=
"Which image gets convolved (default = template)",
102 refObjLoader = pexConfig.ConfigurableField(
103 target=LoadAstrometryNetObjectsTask,
104 doc=
"reference object loader",
106 astrometer = pexConfig.ConfigurableField(
107 target=AstrometryTask,
108 doc=
"astrometry task; used to match sources to reference objects, but not to fit a WCS",
110 sourceSelector = pexConfig.ConfigurableField(
111 target=ObjectSizeStarSelectorTask,
112 doc=
"Source selection algorithm",
114 subtract = pexConfig.ConfigurableField(
115 target=ImagePsfMatchTask,
116 doc=
"Warp and PSF match template to exposure, then subtract",
118 decorrelate = pexConfig.ConfigurableField(
119 target=DecorrelateALKernelTask,
120 doc=
"""Decorrelate effects of A&L kernel convolution on image difference, only if doSubtract is True. 121 If this option is enabled, then detection.thresholdValue should be set to 5.0 (rather than the 124 detection = pexConfig.ConfigurableField(
125 target=SourceDetectionTask,
126 doc=
"Low-threshold detection for final measurement",
132 measurement = pexConfig.ConfigurableField(
133 target=DipoleFitTask,
134 doc=
"Enable updated dipole fitting method.",
136 getTemplate = pexConfig.ConfigurableField(
137 target=GetCoaddAsTemplateTask,
138 doc=
"Subtask to retrieve template exposure and sources",
140 controlStepSize = pexConfig.Field(
141 doc=
"What step size (every Nth one) to select a control sample from the kernelSources",
145 controlRandomSeed = pexConfig.Field(
146 doc =
"Random seed for shuffing the control sample",
150 register = pexConfig.ConfigurableField(
152 doc=
"Task to enable image-to-image image registration (warping)",
154 kernelSourcesFromRef = pexConfig.Field(
155 doc=
"Select sources to measure kernel from reference catalog if True, template if false",
159 templateSipOrder = pexConfig.Field(dtype=int, default=2,
160 doc=
"Sip Order for fitting the Template Wcs " 161 "(default is too high, overfitting)")
163 growFootprint = pexConfig.Field(dtype=int, default=2,
164 doc=
"Grow positive and negative footprints by this amount before merging")
166 diaSourceMatchRadius = pexConfig.Field(dtype=float, default=0.5,
167 doc=
"Match radius (in arcseconds) " 168 "for DiaSource to Source association")
174 self.
subtract.kernel.active.fitForBackground =
True 175 self.
subtract.kernel.active.spatialKernelOrder = 1
176 self.
subtract.kernel.active.spatialBgOrder = 0
183 self.
detection.thresholdPolarity =
"both" 185 self.
detection.reEstimateBackground =
False 186 self.
detection.thresholdType =
"pixel_stdev" 192 self.
measurement.algorithms.names.add(
'base_PeakLikelihoodFlux')
198 pexConfig.Config.validate(self)
200 raise ValueError(
"Cannot run source measurement without source detection.")
202 raise ValueError(
"Cannot run source merging without source detection.")
204 raise ValueError(
"doUseRegister=True and doSelectSources=False. " +
205 "Cannot run RegisterTask without selecting sources.")
212 return pipeBase.TaskRunner.getTargetList(parsedCmd, templateIdList=parsedCmd.templateId.idList,
217 """Subtract an image from a template and measure the result 219 ConfigClass = ImageDifferenceConfig
220 RunnerClass = ImageDifferenceTaskRunner
221 _DefaultName =
"imageDifference" 224 """!Construct an ImageDifference Task 226 @param[in] butler Butler object to use in constructing reference object loaders 228 pipeBase.CmdLineTask.__init__(self, **kwargs)
229 self.makeSubtask(
"subtract")
230 self.makeSubtask(
"getTemplate")
231 self.makeSubtask(
"decorrelate")
233 if self.config.doUseRegister:
234 self.makeSubtask(
"register")
235 self.
schema = afwTable.SourceTable.makeMinimalSchema()
237 if self.config.doSelectSources:
238 self.makeSubtask(
"sourceSelector", schema=self.
schema)
239 self.makeSubtask(
'refObjLoader', butler=butler)
240 self.makeSubtask(
"astrometer", refObjLoader=self.refObjLoader)
243 if self.config.doDetection:
244 self.makeSubtask(
"detection", schema=self.
schema)
245 if self.config.doMeasurement:
246 if not self.config.doDipoleFitting:
247 self.makeSubtask(
"measurement", schema=self.
schema,
250 self.makeSubtask(
"measurement", schema=self.
schema)
251 if self.config.doMatchSources:
252 self.
schema.addField(
"refMatchId",
"L",
"unique id of reference catalog match")
253 self.
schema.addField(
"srcMatchId",
"L",
"unique id of source match")
256 def run(self, sensorRef, templateIdList=None):
257 """Subtract an image from a template coadd and measure the result 260 - warp template coadd to match WCS of image 261 - PSF match image to warped template 262 - subtract image from PSF-matched, warped template 263 - persist difference image 267 @param sensorRef: sensor-level butler data reference, used for the following data products: 273 - self.config.coaddName + "Coadd_skyMap" 274 - self.config.coaddName + "Coadd" 275 Input or output, depending on config: 276 - self.config.coaddName + "Diff_subtractedExp" 277 Output, depending on config: 278 - self.config.coaddName + "Diff_matchedExp" 279 - self.config.coaddName + "Diff_src" 281 @return pipe_base Struct containing these fields: 282 - subtractedExposure: exposure after subtracting template; 283 the unpersisted version if subtraction not run but detection run 284 None if neither subtraction nor detection run (i.e. nothing useful done) 285 - subtractRes: results of subtraction task; None if subtraction not run 286 - sources: detected and possibly measured sources; None if detection not run 288 self.log.info(
"Processing %s" % (sensorRef.dataId))
291 subtractedExposure =
None 295 controlSources =
None 301 expBits = sensorRef.get(
"ccdExposureId_bits")
302 expId = int(sensorRef.get(
"ccdExposureId"))
303 idFactory = afwTable.IdFactory.makeSource(expId, 64 - expBits)
306 exposure = sensorRef.get(
"calexp", immediate=
True)
307 if self.config.doAddCalexpBackground:
308 mi = exposure.getMaskedImage()
309 mi += sensorRef.get(
"calexpBackground").getImage()
310 if not exposure.hasPsf():
311 raise pipeBase.TaskError(
"Exposure has no psf")
312 sciencePsf = exposure.getPsf()
314 subtractedExposureName = self.config.coaddName +
"Diff_differenceExp" 315 templateExposure =
None 316 templateSources =
None 317 if self.config.doSubtract:
318 template = self.getTemplate.
run(exposure, sensorRef, templateIdList=templateIdList)
319 templateExposure = template.exposure
320 templateSources = template.sources
323 scienceSigmaOrig = sciencePsf.computeShape().getDeterminantRadius()
326 templateSigma = templateExposure.getPsf().computeShape().getDeterminantRadius()
332 if self.config.doPreConvolve:
333 convControl = afwMath.ConvolutionControl()
335 srcMI = exposure.getMaskedImage()
336 destMI = srcMI.Factory(srcMI.getDimensions())
338 if self.config.useGaussianForPreConvolution:
340 kWidth, kHeight = sciencePsf.getLocalKernel().getDimensions()
341 preConvPsf = SingleGaussianPsf(kWidth, kHeight, scienceSigmaOrig)
345 afwMath.convolve(destMI, srcMI, preConvPsf.getLocalKernel(), convControl)
346 exposure.setMaskedImage(destMI)
347 scienceSigmaPost = scienceSigmaOrig * math.sqrt(2)
349 scienceSigmaPost = scienceSigmaOrig
352 if self.config.doSelectSources:
353 if not sensorRef.datasetExists(
"src"):
354 self.log.warn(
"Src product does not exist; running detection, measurement, selection")
356 selectSources = self.subtract.getSelectSources(
358 sigma=scienceSigmaPost,
359 doSmooth=
not self.doPreConvolve,
363 self.log.info(
"Source selection via src product")
365 selectSources = sensorRef.get(
"src")
368 nparam = len(makeKernelBasisList(self.subtract.config.kernel.active,
369 referenceFwhmPix=scienceSigmaPost * FwhmPerSigma,
370 targetFwhmPix=templateSigma * FwhmPerSigma))
372 if self.config.doAddMetrics:
374 kcQa = KernelCandidateQa(nparam)
375 selectSources = kcQa.addToSchema(selectSources)
377 if self.config.kernelSourcesFromRef:
379 astromRet = self.astrometer.loadAndMatch(exposure=exposure, sourceCat=selectSources)
380 matches = astromRet.matches
381 elif templateSources:
383 mc = afwTable.MatchControl()
384 mc.findOnlyClosest =
False 385 matches = afwTable.matchRaDec(templateSources, selectSources, 1.0*afwGeom.arcseconds,
388 raise RuntimeError(
"doSelectSources=True and kernelSourcesFromRef=False," +
389 "but template sources not available. Cannot match science " +
390 "sources with template sources. Run process* on data from " +
391 "which templates are built.")
393 kernelSources = self.sourceSelector.selectStars(exposure, selectSources,
394 matches=matches).starCat
396 random.shuffle(kernelSources, random.random)
397 controlSources = kernelSources[::self.config.controlStepSize]
398 kernelSources = [k
for i, k
in enumerate(kernelSources)
if i % self.config.controlStepSize]
400 if self.config.doSelectDcrCatalog:
401 redSelector = DiaCatalogSourceSelectorTask(
402 DiaCatalogSourceSelectorConfig(grMin=self.sourceSelector.config.grMax, grMax=99.999))
403 redSources = redSelector.selectStars(exposure, selectSources, matches=matches).starCat
404 controlSources.extend(redSources)
406 blueSelector = DiaCatalogSourceSelectorTask(
407 DiaCatalogSourceSelectorConfig(grMin=-99.999, grMax=self.sourceSelector.config.grMin))
408 blueSources = blueSelector.selectStars(exposure, selectSources, matches=matches).starCat
409 controlSources.extend(blueSources)
411 if self.config.doSelectVariableCatalog:
412 varSelector = DiaCatalogSourceSelectorTask(
413 DiaCatalogSourceSelectorConfig(includeVariable=
True))
414 varSources = varSelector.selectStars(exposure, selectSources, matches=matches).starCat
415 controlSources.extend(varSources)
417 self.log.info(
"Selected %d / %d sources for Psf matching (%d for control sample)" 418 % (len(kernelSources), len(selectSources), len(controlSources)))
420 if self.config.doUseRegister:
421 self.log.info(
"Registering images")
423 if templateSources
is None:
426 templateSources = self.subtract.getSelectSources(
435 wcsResults = self.
fitAstrometry(templateSources, templateExposure, selectSources)
436 warpedExp = self.register.warpExposure(templateExposure, wcsResults.wcs,
437 exposure.getWcs(), exposure.getBBox())
438 templateExposure = warpedExp
443 if self.config.doDebugRegister:
445 srcToMatch = {x.second.getId(): x.first
for x
in matches}
447 refCoordKey = wcsResults.matches[0].first.getTable().getCoordKey()
448 inCentroidKey = wcsResults.matches[0].second.getTable().getCentroidKey()
449 sids = [m.first.getId()
for m
in wcsResults.matches]
450 positions = [m.first.get(refCoordKey)
for m
in wcsResults.matches]
451 residuals = [m.first.get(refCoordKey).getOffsetFrom(wcsResults.wcs.pixelToSky(
452 m.second.get(inCentroidKey)))
for m
in wcsResults.matches]
453 allresids = dict(zip(sids, zip(positions, residuals)))
455 cresiduals = [m.first.get(refCoordKey).getTangentPlaneOffset(
456 wcsResults.wcs.pixelToSky(
457 m.second.get(inCentroidKey)))
for m
in wcsResults.matches]
458 colors = numpy.array([-2.5*numpy.log10(srcToMatch[x].get(
"g")) +
459 2.5*numpy.log10(srcToMatch[x].get(
"r")) 460 for x
in sids
if x
in srcToMatch.keys()])
461 dlong = numpy.array([r[0].asArcseconds()
for s, r
in zip(sids, cresiduals)
462 if s
in srcToMatch.keys()])
463 dlat = numpy.array([r[1].asArcseconds()
for s, r
in zip(sids, cresiduals)
464 if s
in srcToMatch.keys()])
465 idx1 = numpy.where(colors < self.sourceSelector.config.grMin)
466 idx2 = numpy.where((colors >= self.sourceSelector.config.grMin) &
467 (colors <= self.sourceSelector.config.grMax))
468 idx3 = numpy.where(colors > self.sourceSelector.config.grMax)
469 rms1Long = IqrToSigma * \
470 (numpy.percentile(dlong[idx1], 75)-numpy.percentile(dlong[idx1], 25))
471 rms1Lat = IqrToSigma*(numpy.percentile(dlat[idx1], 75)-numpy.percentile(dlat[idx1], 25))
472 rms2Long = IqrToSigma * \
473 (numpy.percentile(dlong[idx2], 75)-numpy.percentile(dlong[idx2], 25))
474 rms2Lat = IqrToSigma*(numpy.percentile(dlat[idx2], 75)-numpy.percentile(dlat[idx2], 25))
475 rms3Long = IqrToSigma * \
476 (numpy.percentile(dlong[idx3], 75)-numpy.percentile(dlong[idx3], 25))
477 rms3Lat = IqrToSigma*(numpy.percentile(dlat[idx3], 75)-numpy.percentile(dlat[idx3], 25))
478 self.log.info(
"Blue star offsets'': %.3f %.3f, %.3f %.3f" % (numpy.median(dlong[idx1]),
480 numpy.median(dlat[idx1]),
482 self.log.info(
"Green star offsets'': %.3f %.3f, %.3f %.3f" % (numpy.median(dlong[idx2]),
484 numpy.median(dlat[idx2]),
486 self.log.info(
"Red star offsets'': %.3f %.3f, %.3f %.3f" % (numpy.median(dlong[idx3]),
488 numpy.median(dlat[idx3]),
491 self.metadata.add(
"RegisterBlueLongOffsetMedian", numpy.median(dlong[idx1]))
492 self.metadata.add(
"RegisterGreenLongOffsetMedian", numpy.median(dlong[idx2]))
493 self.metadata.add(
"RegisterRedLongOffsetMedian", numpy.median(dlong[idx3]))
494 self.metadata.add(
"RegisterBlueLongOffsetStd", rms1Long)
495 self.metadata.add(
"RegisterGreenLongOffsetStd", rms2Long)
496 self.metadata.add(
"RegisterRedLongOffsetStd", rms3Long)
498 self.metadata.add(
"RegisterBlueLatOffsetMedian", numpy.median(dlat[idx1]))
499 self.metadata.add(
"RegisterGreenLatOffsetMedian", numpy.median(dlat[idx2]))
500 self.metadata.add(
"RegisterRedLatOffsetMedian", numpy.median(dlat[idx3]))
501 self.metadata.add(
"RegisterBlueLatOffsetStd", rms1Lat)
502 self.metadata.add(
"RegisterGreenLatOffsetStd", rms2Lat)
503 self.metadata.add(
"RegisterRedLatOffsetStd", rms3Lat)
510 self.log.info(
"Subtracting images")
511 subtractRes = self.subtract.subtractExposures(
512 templateExposure=templateExposure,
513 scienceExposure=exposure,
514 candidateList=kernelSources,
515 convolveTemplate=self.config.convolveTemplate,
516 doWarping=
not self.config.doUseRegister
518 subtractedExposure = subtractRes.subtractedExposure
520 if self.config.doWriteMatchedExp:
521 sensorRef.put(subtractRes.matchedExposure, self.config.coaddName +
"Diff_matchedExp")
523 if self.config.doDetection:
524 self.log.info(
"Computing diffim PSF")
525 if subtractedExposure
is None:
526 subtractedExposure = sensorRef.get(subtractedExposureName)
529 if not subtractedExposure.hasPsf():
530 if self.config.convolveTemplate:
531 subtractedExposure.setPsf(exposure.getPsf())
533 if templateExposure
is None:
534 template = self.getTemplate.
run(exposure, sensorRef, templateIdList=templateIdList)
535 subtractedExposure.setPsf(template.exposure.getPsf())
539 if self.config.doDecorrelation
and self.config.doSubtract:
540 decorrResult = self.decorrelate.
run(exposure, templateExposure,
542 subtractRes.psfMatchingKernel)
543 subtractedExposure = decorrResult.correctedExposure
545 if self.config.doDetection:
546 self.log.info(
"Running diaSource detection")
548 mask = subtractedExposure.getMaskedImage().getMask()
549 mask &= ~(mask.getPlaneBitMask(
"DETECTED") | mask.getPlaneBitMask(
"DETECTED_NEGATIVE"))
551 table = afwTable.SourceTable.make(self.
schema, idFactory)
553 results = self.detection.makeSourceCatalog(
555 exposure=subtractedExposure,
556 doSmooth=
not self.config.doPreConvolve
559 if self.config.doMerge:
560 fpSet = results.fpSets.positive
561 fpSet.merge(results.fpSets.negative, self.config.growFootprint,
562 self.config.growFootprint,
False)
563 diaSources = afwTable.SourceCatalog(table)
564 fpSet.makeSources(diaSources)
565 self.log.info(
"Merging detections into %d sources" % (len(diaSources)))
567 diaSources = results.sources
569 if self.config.doMeasurement:
570 self.log.info(
"Running diaSource measurement")
571 if not self.config.doDipoleFitting:
572 self.measurement.
run(diaSources, subtractedExposure)
574 if self.config.doSubtract:
575 self.measurement.
run(diaSources, subtractedExposure, exposure,
576 subtractRes.matchedExposure)
578 self.measurement.
run(diaSources, subtractedExposure, exposure)
581 if self.config.doMatchSources:
582 if sensorRef.datasetExists(
"src"):
584 matchRadAsec = self.config.diaSourceMatchRadius
585 matchRadPixel = matchRadAsec / exposure.getWcs().pixelScale().asArcseconds()
587 srcMatches = afwTable.matchXy(sensorRef.get(
"src"), diaSources, matchRadPixel)
588 srcMatchDict = dict([(srcMatch.second.getId(), srcMatch.first.getId())
for 589 srcMatch
in srcMatches])
590 self.log.info(
"Matched %d / %d diaSources to sources" % (len(srcMatchDict),
593 self.log.warn(
"Src product does not exist; cannot match with diaSources")
597 refAstromConfig = AstrometryConfig()
598 refAstromConfig.matcher.maxMatchDistArcSec = matchRadAsec
599 refAstrometer = AstrometryTask(refAstromConfig)
600 astromRet = refAstrometer.run(exposure=exposure, sourceCat=diaSources)
601 refMatches = astromRet.matches
602 if refMatches
is None:
603 self.log.warn(
"No diaSource matches with reference catalog")
606 self.log.info(
"Matched %d / %d diaSources to reference catalog" % (len(refMatches),
608 refMatchDict = dict([(refMatch.second.getId(), refMatch.first.getId())
for 609 refMatch
in refMatches])
612 for diaSource
in diaSources:
613 sid = diaSource.getId()
614 if sid
in srcMatchDict:
615 diaSource.set(
"srcMatchId", srcMatchDict[sid])
616 if sid
in refMatchDict:
617 diaSource.set(
"refMatchId", refMatchDict[sid])
619 if diaSources
is not None and self.config.doWriteSources:
620 sensorRef.put(diaSources, self.config.coaddName +
"Diff_diaSrc")
622 if self.config.doAddMetrics
and self.config.doSelectSources:
623 self.log.info(
"Evaluating metrics and control sample")
626 for cell
in subtractRes.kernelCellSet.getCellList():
627 for cand
in cell.begin(
False):
628 kernelCandList.append(cand)
631 basisList = kernelCandList[0].getKernel(KernelCandidateF.ORIG).getKernelList()
634 diffimTools.sourceTableToCandidateList(controlSources,
635 subtractRes.warpedExposure, exposure,
636 self.config.subtract.kernel.active,
637 self.config.subtract.kernel.active.detectionConfig,
638 self.log, doBuild=
True, basisList=basisList)
640 kcQa.apply(kernelCandList, subtractRes.psfMatchingKernel, subtractRes.backgroundModel,
642 kcQa.apply(controlCandList, subtractRes.psfMatchingKernel, subtractRes.backgroundModel)
644 if self.config.doDetection:
645 kcQa.aggregate(selectSources, self.metadata, allresids, diaSources)
647 kcQa.aggregate(selectSources, self.metadata, allresids)
649 sensorRef.put(selectSources, self.config.coaddName +
"Diff_kernelSrc")
651 if self.config.doWriteSubtractedExp:
652 sensorRef.put(subtractedExposure, subtractedExposureName)
654 self.
runDebug(exposure, subtractRes, selectSources, kernelSources, diaSources)
655 return pipeBase.Struct(
656 subtractedExposure=subtractedExposure,
657 subtractRes=subtractRes,
662 """Fit the relative astrometry between templateSources and selectSources 664 @todo remove this method. It originally fit a new WCS to the template before calling register.run 665 because our TAN-SIP fitter behaved badly for points far from CRPIX, but that's been fixed. 666 It remains because a subtask overrides it. 668 results = self.register.
run(templateSources, templateExposure.getWcs(),
669 templateExposure.getBBox(), selectSources)
672 def runDebug(self, exposure, subtractRes, selectSources, kernelSources, diaSources):
673 """@todo Test and update for current debug display and slot names 676 display = lsstDebug.Info(__name__).display
677 showSubtracted = lsstDebug.Info(__name__).showSubtracted
678 showPixelResiduals = lsstDebug.Info(__name__).showPixelResiduals
679 showDiaSources = lsstDebug.Info(__name__).showDiaSources
680 showDipoles = lsstDebug.Info(__name__).showDipoles
681 maskTransparency = lsstDebug.Info(__name__).maskTransparency
683 import lsst.afw.display.ds9
as ds9
684 if not maskTransparency:
686 ds9.setMaskTransparency(maskTransparency)
688 if display
and showSubtracted:
689 ds9.mtv(subtractRes.subtractedExposure, frame=lsstDebug.frame, title=
"Subtracted image")
690 mi = subtractRes.subtractedExposure.getMaskedImage()
691 x0, y0 = mi.getX0(), mi.getY0()
692 with ds9.Buffering():
694 x, y = s.getX() - x0, s.getY() - y0
695 ctype =
"red" if s.get(
"flags.negative")
else "yellow" 696 if (s.get(
"flags.pixel.interpolated.center")
or s.get(
"flags.pixel.saturated.center")
or 697 s.get(
"flags.pixel.cr.center")):
699 elif (s.get(
"flags.pixel.interpolated.any")
or s.get(
"flags.pixel.saturated.any")
or 700 s.get(
"flags.pixel.cr.any")):
704 ds9.dot(ptype, x, y, size=4, frame=lsstDebug.frame, ctype=ctype)
707 if display
and showPixelResiduals
and selectSources:
708 nonKernelSources = []
709 for source
in selectSources:
710 if source
not in kernelSources:
711 nonKernelSources.append(source)
713 diUtils.plotPixelResiduals(exposure,
714 subtractRes.warpedExposure,
715 subtractRes.subtractedExposure,
716 subtractRes.kernelCellSet,
717 subtractRes.psfMatchingKernel,
718 subtractRes.backgroundModel,
720 self.subtract.config.kernel.active.detectionConfig,
722 diUtils.plotPixelResiduals(exposure,
723 subtractRes.warpedExposure,
724 subtractRes.subtractedExposure,
725 subtractRes.kernelCellSet,
726 subtractRes.psfMatchingKernel,
727 subtractRes.backgroundModel,
729 self.subtract.config.kernel.active.detectionConfig,
731 if display
and showDiaSources:
732 flagChecker = SourceFlagChecker(diaSources)
733 isFlagged = [flagChecker(x)
for x
in diaSources]
734 isDipole = [x.get(
"classification.dipole")
for x
in diaSources]
735 diUtils.showDiaSources(diaSources, subtractRes.subtractedExposure, isFlagged, isDipole,
736 frame=lsstDebug.frame)
739 if display
and showDipoles:
740 DipoleAnalysis().displayDipoles(subtractRes.subtractedExposure, diaSources,
741 frame=lsstDebug.frame)
744 def _getConfigName(self):
745 """Return the name of the config dataset 747 return "%sDiff_config" % (self.config.coaddName,)
749 def _getMetadataName(self):
750 """Return the name of the metadata dataset 752 return "%sDiff_metadata" % (self.config.coaddName,)
755 """Return a dict of empty catalogs for each catalog dataset produced by this task.""" 756 diaSrc = afwTable.SourceCatalog(self.
schema)
758 return {self.config.coaddName +
"Diff_diaSrc": diaSrc}
761 def _makeArgumentParser(cls):
762 """Create an argument parser 765 parser.add_id_argument(
"--id",
"calexp", help=
"data ID, e.g. --id visit=12345 ccd=1,2")
766 parser.add_id_argument(
"--templateId",
"calexp", doMakeDataRefList=
True,
767 help=
"Optional template data ID (visit only), e.g. --templateId visit=6789")
772 winter2013WcsShift = pexConfig.Field(dtype=float, default=0.0,
773 doc=
"Shift stars going into RegisterTask by this amount")
774 winter2013WcsRms = pexConfig.Field(dtype=float, default=0.0,
775 doc=
"Perturb stars going into RegisterTask by this amount")
778 ImageDifferenceConfig.setDefaults(self)
783 """!Image difference Task used in the Winter 2013 data challege. 784 Enables testing the effects of registration shifts and scatter. 786 For use with winter 2013 simulated images: 787 Use --templateId visit=88868666 for sparse data 788 --templateId visit=22222200 for dense data (g) 789 --templateId visit=11111100 for dense data (i) 791 ConfigClass = Winter2013ImageDifferenceConfig
792 _DefaultName =
"winter2013ImageDifference" 795 ImageDifferenceTask.__init__(self, **kwargs)
798 """Fit the relative astrometry between templateSources and selectSources""" 799 if self.config.winter2013WcsShift > 0.0:
800 offset = afwGeom.Extent2D(self.config.winter2013WcsShift,
801 self.config.winter2013WcsShift)
802 cKey = templateSources[0].getTable().getCentroidKey()
803 for source
in templateSources:
804 centroid = source.get(cKey)
805 source.set(cKey, centroid+offset)
806 elif self.config.winter2013WcsRms > 0.0:
807 cKey = templateSources[0].getTable().getCentroidKey()
808 for source
in templateSources:
809 offset = afwGeom.Extent2D(self.config.winter2013WcsRms*numpy.random.normal(),
810 self.config.winter2013WcsRms*numpy.random.normal())
811 centroid = source.get(cKey)
812 source.set(cKey, centroid+offset)
814 results = self.register.
run(templateSources, templateExposure.getWcs(),
815 templateExposure.getBBox(), selectSources)
def __init__(self, butler=None, kwargs)
Construct an ImageDifference Task.
def run(self, sensorRef, templateIdList=None)
def fitAstrometry(self, templateSources, templateExposure, selectSources)
def __init__(self, kwargs)
def getSchemaCatalogs(self)
def runDebug(self, exposure, subtractRes, selectSources, kernelSources, diaSources)
Image difference Task used in the Winter 2013 data challege.
def fitAstrometry(self, templateSources, templateExposure, selectSources)
def getTargetList(parsedCmd, kwargs)