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 DipoleAnalysis, \
40 SourceFlagChecker, KernelCandidateF, makeKernelBasisList, \
41 KernelCandidateQa, DiaCatalogSourceSelectorTask, DiaCatalogSourceSelectorConfig, \
42 GetCoaddAsTemplateTask, GetCalexpAsTemplateTask, DipoleFitTask, \
43 DecorrelateALKernelSpatialTask, subtractAlgorithmRegistry
44 import lsst.ip.diffim.diffimTools
as diffimTools
45 import lsst.ip.diffim.utils
as diUtils
47 FwhmPerSigma = 2 * math.sqrt(2 * math.log(2))
52 """Config for ImageDifferenceTask
54 doAddCalexpBackground = pexConfig.Field(dtype=bool, default=
True,
55 doc=
"Add background to calexp before processing it. "
56 "Useful as ipDiffim does background matching.")
57 doUseRegister = pexConfig.Field(dtype=bool, default=
True,
58 doc=
"Use image-to-image registration to align template with "
60 doDebugRegister = pexConfig.Field(dtype=bool, default=
False,
61 doc=
"Writing debugging data for doUseRegister")
62 doSelectSources = pexConfig.Field(dtype=bool, default=
True,
63 doc=
"Select stars to use for kernel fitting")
64 doSelectDcrCatalog = pexConfig.Field(dtype=bool, default=
False,
65 doc=
"Select stars of extreme color as part of the control sample")
66 doSelectVariableCatalog = pexConfig.Field(dtype=bool, default=
False,
67 doc=
"Select stars that are variable to be part "
68 "of the control sample")
69 doSubtract = pexConfig.Field(dtype=bool, default=
True, doc=
"Compute subtracted exposure?")
70 doPreConvolve = pexConfig.Field(dtype=bool, default=
True,
71 doc=
"Convolve science image by its PSF before PSF-matching?")
72 useGaussianForPreConvolution = pexConfig.Field(dtype=bool, default=
True,
73 doc=
"Use a simple gaussian PSF model for pre-convolution "
74 "(else use fit PSF)? Ignored if doPreConvolve false.")
75 doDetection = pexConfig.Field(dtype=bool, default=
True, doc=
"Detect sources?")
76 doDecorrelation = pexConfig.Field(dtype=bool, default=
False,
77 doc=
"Perform diffim decorrelation to undo pixel correlation due to A&L "
78 "kernel convolution? If True, also update the diffim PSF.")
79 doMerge = pexConfig.Field(dtype=bool, default=
True,
80 doc=
"Merge positive and negative diaSources with grow radius "
81 "set by growFootprint")
82 doMatchSources = pexConfig.Field(dtype=bool, default=
True,
83 doc=
"Match diaSources with input calexp sources and ref catalog sources")
84 doMeasurement = pexConfig.Field(dtype=bool, default=
True, doc=
"Measure diaSources?")
85 doDipoleFitting = pexConfig.Field(dtype=bool, default=
True, doc=
"Measure dipoles using new algorithm?")
86 doWriteSubtractedExp = pexConfig.Field(dtype=bool, default=
True, doc=
"Write difference exposure?")
87 doWriteMatchedExp = pexConfig.Field(dtype=bool, default=
False,
88 doc=
"Write warped and PSF-matched template coadd exposure?")
89 doWriteSources = pexConfig.Field(dtype=bool, default=
True, doc=
"Write sources?")
90 doAddMetrics = pexConfig.Field(dtype=bool, default=
True,
91 doc=
"Add columns to the source table to hold analysis metrics?")
93 coaddName = pexConfig.Field(
94 doc=
"coadd name: typically one of deep or goodSeeing",
98 convolveTemplate = pexConfig.Field(
99 doc=
"Which image gets convolved (default = template)",
103 refObjLoader = pexConfig.ConfigurableField(
104 target=LoadAstrometryNetObjectsTask,
105 doc=
"reference object loader",
107 astrometer = pexConfig.ConfigurableField(
108 target=AstrometryTask,
109 doc=
"astrometry task; used to match sources to reference objects, but not to fit a WCS",
111 sourceSelector = pexConfig.ConfigurableField(
112 target=ObjectSizeStarSelectorTask,
113 doc=
"Source selection algorithm",
115 subtract = subtractAlgorithmRegistry.makeField(
"Subtraction Algorithm", default=
"al")
116 decorrelate = pexConfig.ConfigurableField(
117 target=DecorrelateALKernelSpatialTask,
118 doc=
"Decorrelate effects of A&L kernel convolution on image difference, only if doSubtract is True. "
119 "If this option is enabled, then detection.thresholdValue should be set to 5.0 (rather than the "
122 doSpatiallyVarying = pexConfig.Field(
125 doc=
"If using Zogy or A&L decorrelation, perform these on a grid across the "
126 "image in order to allow for spatial variations"
128 detection = pexConfig.ConfigurableField(
129 target=SourceDetectionTask,
130 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")
173 self.
subtract[
'al'].kernel.name =
"AL"
174 self.
subtract[
'al'].kernel.active.fitForBackground =
True
175 self.
subtract[
'al'].kernel.active.spatialKernelOrder = 1
176 self.
subtract[
'al'].kernel.active.spatialBgOrder = 0
183 self.detection.thresholdPolarity =
"both"
184 self.detection.thresholdValue = 5.5
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(
"getTemplate")
231 self.makeSubtask(
"subtract")
233 if self.config.subtract.name ==
'al' and self.config.doDecorrelation:
234 self.makeSubtask(
"decorrelate")
236 if self.config.doUseRegister:
237 self.makeSubtask(
"register")
238 self.
schema = afwTable.SourceTable.makeMinimalSchema()
240 if self.config.doSelectSources:
241 self.makeSubtask(
"sourceSelector", schema=self.
schema)
242 self.makeSubtask(
'refObjLoader', butler=butler)
243 self.makeSubtask(
"astrometer", refObjLoader=self.refObjLoader)
246 if self.config.doDetection:
247 self.makeSubtask(
"detection", schema=self.
schema)
248 if self.config.doMeasurement:
249 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
322 if self.config.subtract.name ==
'zogy':
323 spatiallyVarying = self.config.doSpatiallyVarying
324 subtractRes = self.subtract.subtractExposures(templateExposure, exposure,
326 spatiallyVarying=spatiallyVarying)
327 subtractedExposure = subtractRes.subtractedExposure
329 elif self.config.subtract.name ==
'al':
331 scienceSigmaOrig = sciencePsf.computeShape().getDeterminantRadius()
334 templateSigma = templateExposure.getPsf().computeShape().getDeterminantRadius()
340 if self.config.doPreConvolve:
341 convControl = afwMath.ConvolutionControl()
343 srcMI = exposure.getMaskedImage()
344 destMI = srcMI.Factory(srcMI.getDimensions())
346 if self.config.useGaussianForPreConvolution:
348 kWidth, kHeight = sciencePsf.getLocalKernel().getDimensions()
349 preConvPsf = SingleGaussianPsf(kWidth, kHeight, scienceSigmaOrig)
353 afwMath.convolve(destMI, srcMI, preConvPsf.getLocalKernel(), convControl)
354 exposure.setMaskedImage(destMI)
355 scienceSigmaPost = scienceSigmaOrig * math.sqrt(2)
357 scienceSigmaPost = scienceSigmaOrig
360 if self.config.doSelectSources:
361 if not sensorRef.datasetExists(
"src"):
362 self.log.warn(
"Src product does not exist; running detection, measurement, selection")
364 selectSources = self.subtract.getSelectSources(
366 sigma=scienceSigmaPost,
367 doSmooth=
not self.doPreConvolve,
371 self.log.info(
"Source selection via src product")
373 selectSources = sensorRef.get(
"src")
376 nparam = len(makeKernelBasisList(self.subtract.config.kernel.active,
377 referenceFwhmPix=scienceSigmaPost * FwhmPerSigma,
378 targetFwhmPix=templateSigma * FwhmPerSigma))
380 if self.config.doAddMetrics:
382 kcQa = KernelCandidateQa(nparam)
383 selectSources = kcQa.addToSchema(selectSources)
385 if self.config.kernelSourcesFromRef:
387 astromRet = self.astrometer.loadAndMatch(exposure=exposure, sourceCat=selectSources)
388 matches = astromRet.matches
389 elif templateSources:
391 mc = afwTable.MatchControl()
392 mc.findOnlyClosest =
False
393 matches = afwTable.matchRaDec(templateSources, selectSources, 1.0*afwGeom.arcseconds,
396 raise RuntimeError(
"doSelectSources=True and kernelSourcesFromRef=False," +
397 "but template sources not available. Cannot match science " +
398 "sources with template sources. Run process* on data from " +
399 "which templates are built.")
401 kernelSources = self.sourceSelector.selectStars(exposure, selectSources,
402 matches=matches).starCat
404 random.shuffle(kernelSources, random.random)
405 controlSources = kernelSources[::self.config.controlStepSize]
406 kernelSources = [k
for i, k
in enumerate(kernelSources)
if i % self.config.controlStepSize]
408 if self.config.doSelectDcrCatalog:
409 redSelector = DiaCatalogSourceSelectorTask(
410 DiaCatalogSourceSelectorConfig(grMin=self.sourceSelector.config.grMax, grMax=99.999))
411 redSources = redSelector.selectStars(exposure, selectSources, matches=matches).starCat
412 controlSources.extend(redSources)
414 blueSelector = DiaCatalogSourceSelectorTask(
415 DiaCatalogSourceSelectorConfig(grMin=-99.999, grMax=self.sourceSelector.config.grMin))
416 blueSources = blueSelector.selectStars(exposure, selectSources, matches=matches).starCat
417 controlSources.extend(blueSources)
419 if self.config.doSelectVariableCatalog:
420 varSelector = DiaCatalogSourceSelectorTask(
421 DiaCatalogSourceSelectorConfig(includeVariable=
True))
422 varSources = varSelector.selectStars(exposure, selectSources, matches=matches).starCat
423 controlSources.extend(varSources)
425 self.log.info(
"Selected %d / %d sources for Psf matching (%d for control sample)"
426 % (len(kernelSources), len(selectSources), len(controlSources)))
428 if self.config.doUseRegister:
429 self.log.info(
"Registering images")
431 if templateSources
is None:
434 templateSources = self.subtract.getSelectSources(
443 wcsResults = self.
fitAstrometry(templateSources, templateExposure, selectSources)
444 warpedExp = self.register.warpExposure(templateExposure, wcsResults.wcs,
445 exposure.getWcs(), exposure.getBBox())
446 templateExposure = warpedExp
451 if self.config.doDebugRegister:
453 srcToMatch = {x.second.getId(): x.first
for x
in matches}
455 refCoordKey = wcsResults.matches[0].first.getTable().getCoordKey()
456 inCentroidKey = wcsResults.matches[0].second.getTable().getCentroidKey()
457 sids = [m.first.getId()
for m
in wcsResults.matches]
458 positions = [m.first.get(refCoordKey)
for m
in wcsResults.matches]
459 residuals = [m.first.get(refCoordKey).getOffsetFrom(wcsResults.wcs.pixelToSky(
460 m.second.get(inCentroidKey)))
for m
in wcsResults.matches]
461 allresids = dict(zip(sids, zip(positions, residuals)))
463 cresiduals = [m.first.get(refCoordKey).getTangentPlaneOffset(
464 wcsResults.wcs.pixelToSky(
465 m.second.get(inCentroidKey)))
for m
in wcsResults.matches]
466 colors = numpy.array([-2.5*numpy.log10(srcToMatch[x].get(
"g")) +
467 2.5*numpy.log10(srcToMatch[x].get(
"r"))
468 for x
in sids
if x
in srcToMatch.keys()])
469 dlong = numpy.array([r[0].asArcseconds()
for s, r
in zip(sids, cresiduals)
470 if s
in srcToMatch.keys()])
471 dlat = numpy.array([r[1].asArcseconds()
for s, r
in zip(sids, cresiduals)
472 if s
in srcToMatch.keys()])
473 idx1 = numpy.where(colors < self.sourceSelector.config.grMin)
474 idx2 = numpy.where((colors >= self.sourceSelector.config.grMin) &
475 (colors <= self.sourceSelector.config.grMax))
476 idx3 = numpy.where(colors > self.sourceSelector.config.grMax)
477 rms1Long = IqrToSigma * \
478 (numpy.percentile(dlong[idx1], 75)-numpy.percentile(dlong[idx1], 25))
479 rms1Lat = IqrToSigma*(numpy.percentile(dlat[idx1], 75)-numpy.percentile(dlat[idx1], 25))
480 rms2Long = IqrToSigma * \
481 (numpy.percentile(dlong[idx2], 75)-numpy.percentile(dlong[idx2], 25))
482 rms2Lat = IqrToSigma*(numpy.percentile(dlat[idx2], 75)-numpy.percentile(dlat[idx2], 25))
483 rms3Long = IqrToSigma * \
484 (numpy.percentile(dlong[idx3], 75)-numpy.percentile(dlong[idx3], 25))
485 rms3Lat = IqrToSigma*(numpy.percentile(dlat[idx3], 75)-numpy.percentile(dlat[idx3], 25))
486 self.log.info(
"Blue star offsets'': %.3f %.3f, %.3f %.3f" % (numpy.median(dlong[idx1]),
488 numpy.median(dlat[idx1]),
490 self.log.info(
"Green star offsets'': %.3f %.3f, %.3f %.3f" % (numpy.median(dlong[idx2]),
492 numpy.median(dlat[idx2]),
494 self.log.info(
"Red star offsets'': %.3f %.3f, %.3f %.3f" % (numpy.median(dlong[idx3]),
496 numpy.median(dlat[idx3]),
499 self.metadata.add(
"RegisterBlueLongOffsetMedian", numpy.median(dlong[idx1]))
500 self.metadata.add(
"RegisterGreenLongOffsetMedian", numpy.median(dlong[idx2]))
501 self.metadata.add(
"RegisterRedLongOffsetMedian", numpy.median(dlong[idx3]))
502 self.metadata.add(
"RegisterBlueLongOffsetStd", rms1Long)
503 self.metadata.add(
"RegisterGreenLongOffsetStd", rms2Long)
504 self.metadata.add(
"RegisterRedLongOffsetStd", rms3Long)
506 self.metadata.add(
"RegisterBlueLatOffsetMedian", numpy.median(dlat[idx1]))
507 self.metadata.add(
"RegisterGreenLatOffsetMedian", numpy.median(dlat[idx2]))
508 self.metadata.add(
"RegisterRedLatOffsetMedian", numpy.median(dlat[idx3]))
509 self.metadata.add(
"RegisterBlueLatOffsetStd", rms1Lat)
510 self.metadata.add(
"RegisterGreenLatOffsetStd", rms2Lat)
511 self.metadata.add(
"RegisterRedLatOffsetStd", rms3Lat)
518 self.log.info(
"Subtracting images")
519 subtractRes = self.subtract.subtractExposures(
520 templateExposure=templateExposure,
521 scienceExposure=exposure,
522 candidateList=kernelSources,
523 convolveTemplate=self.config.convolveTemplate,
524 doWarping=
not self.config.doUseRegister
526 subtractedExposure = subtractRes.subtractedExposure
528 if self.config.doWriteMatchedExp:
529 sensorRef.put(subtractRes.matchedExposure, self.config.coaddName +
"Diff_matchedExp")
531 if self.config.doDetection:
532 self.log.info(
"Computing diffim PSF")
533 if subtractedExposure
is None:
534 subtractedExposure = sensorRef.get(subtractedExposureName)
537 if not subtractedExposure.hasPsf():
538 if self.config.convolveTemplate:
539 subtractedExposure.setPsf(exposure.getPsf())
541 if templateExposure
is None:
542 template = self.getTemplate.run(exposure, sensorRef,
543 templateIdList=templateIdList)
544 subtractedExposure.setPsf(template.exposure.getPsf())
549 if self.config.doDecorrelation
and self.config.doSubtract:
550 spatiallyVarying = self.config.doSpatiallyVarying
551 decorrResult = self.decorrelate.run(exposure, templateExposure,
553 subtractRes.psfMatchingKernel,
554 spatiallyVarying=spatiallyVarying)
555 subtractedExposure = decorrResult.correctedExposure
559 if self.config.doDetection:
560 self.log.info(
"Running diaSource detection")
562 mask = subtractedExposure.getMaskedImage().getMask()
563 mask &= ~(mask.getPlaneBitMask(
"DETECTED") | mask.getPlaneBitMask(
"DETECTED_NEGATIVE"))
565 table = afwTable.SourceTable.make(self.
schema, idFactory)
567 results = self.detection.makeSourceCatalog(
569 exposure=subtractedExposure,
570 doSmooth=
not self.config.doPreConvolve
573 if self.config.doMerge:
574 fpSet = results.fpSets.positive
575 fpSet.merge(results.fpSets.negative, self.config.growFootprint,
576 self.config.growFootprint,
False)
577 diaSources = afwTable.SourceCatalog(table)
578 fpSet.makeSources(diaSources)
579 self.log.info(
"Merging detections into %d sources" % (len(diaSources)))
581 diaSources = results.sources
583 if self.config.doMeasurement:
584 newDipoleFitting = self.config.doDipoleFitting
585 self.log.info(
"Running diaSource measurement: newDipoleFitting=%r", newDipoleFitting)
586 if not newDipoleFitting:
588 self.measurement.run(diaSources, subtractedExposure)
591 if self.config.doSubtract
and 'matchedExposure' in subtractRes.getDict():
592 self.measurement.run(diaSources, subtractedExposure, exposure,
593 subtractRes.matchedExposure)
595 self.measurement.run(diaSources, subtractedExposure, exposure)
598 if self.config.doMatchSources:
599 if sensorRef.datasetExists(
"src"):
601 matchRadAsec = self.config.diaSourceMatchRadius
602 matchRadPixel = matchRadAsec / exposure.getWcs().pixelScale().asArcseconds()
604 srcMatches = afwTable.matchXy(sensorRef.get(
"src"), diaSources, matchRadPixel)
605 srcMatchDict = dict([(srcMatch.second.getId(), srcMatch.first.getId())
for
606 srcMatch
in srcMatches])
607 self.log.info(
"Matched %d / %d diaSources to sources" % (len(srcMatchDict),
610 self.log.warn(
"Src product does not exist; cannot match with diaSources")
614 refAstromConfig = AstrometryConfig()
615 refAstromConfig.matcher.maxMatchDistArcSec = matchRadAsec
616 refAstrometer = AstrometryTask(refAstromConfig)
617 astromRet = refAstrometer.run(exposure=exposure, sourceCat=diaSources)
618 refMatches = astromRet.matches
619 if refMatches
is None:
620 self.log.warn(
"No diaSource matches with reference catalog")
623 self.log.info(
"Matched %d / %d diaSources to reference catalog" % (len(refMatches),
625 refMatchDict = dict([(refMatch.second.getId(), refMatch.first.getId())
for
626 refMatch
in refMatches])
629 for diaSource
in diaSources:
630 sid = diaSource.getId()
631 if sid
in srcMatchDict:
632 diaSource.set(
"srcMatchId", srcMatchDict[sid])
633 if sid
in refMatchDict:
634 diaSource.set(
"refMatchId", refMatchDict[sid])
636 if diaSources
is not None and self.config.doWriteSources:
637 sensorRef.put(diaSources, self.config.coaddName +
"Diff_diaSrc")
639 if self.config.doAddMetrics
and self.config.doSelectSources:
640 self.log.info(
"Evaluating metrics and control sample")
643 for cell
in subtractRes.kernelCellSet.getCellList():
644 for cand
in cell.begin(
False):
645 kernelCandList.append(cand)
648 basisList = kernelCandList[0].getKernel(KernelCandidateF.ORIG).getKernelList()
651 diffimTools.sourceTableToCandidateList(controlSources,
652 subtractRes.warpedExposure, exposure,
653 self.config.subtract.kernel.active,
654 self.config.subtract.kernel.active.detectionConfig,
655 self.log, doBuild=
True, basisList=basisList)
657 kcQa.apply(kernelCandList, subtractRes.psfMatchingKernel, subtractRes.backgroundModel,
659 kcQa.apply(controlCandList, subtractRes.psfMatchingKernel, subtractRes.backgroundModel)
661 if self.config.doDetection:
662 kcQa.aggregate(selectSources, self.metadata, allresids, diaSources)
664 kcQa.aggregate(selectSources, self.metadata, allresids)
666 sensorRef.put(selectSources, self.config.coaddName +
"Diff_kernelSrc")
668 if self.config.doWriteSubtractedExp:
669 sensorRef.put(subtractedExposure, subtractedExposureName)
671 self.
runDebug(exposure, subtractRes, selectSources, kernelSources, diaSources)
672 return pipeBase.Struct(
673 subtractedExposure=subtractedExposure,
674 subtractRes=subtractRes,
679 """Fit the relative astrometry between templateSources and selectSources
681 @todo remove this method. It originally fit a new WCS to the template before calling register.run
682 because our TAN-SIP fitter behaved badly for points far from CRPIX, but that's been fixed.
683 It remains because a subtask overrides it.
685 results = self.register.run(templateSources, templateExposure.getWcs(),
686 templateExposure.getBBox(), selectSources)
689 def runDebug(self, exposure, subtractRes, selectSources, kernelSources, diaSources):
690 """@todo Test and update for current debug display and slot names
693 display = lsstDebug.Info(__name__).display
694 showSubtracted = lsstDebug.Info(__name__).showSubtracted
695 showPixelResiduals = lsstDebug.Info(__name__).showPixelResiduals
696 showDiaSources = lsstDebug.Info(__name__).showDiaSources
697 showDipoles = lsstDebug.Info(__name__).showDipoles
698 maskTransparency = lsstDebug.Info(__name__).maskTransparency
700 import lsst.afw.display.ds9
as ds9
701 if not maskTransparency:
703 ds9.setMaskTransparency(maskTransparency)
705 if display
and showSubtracted:
706 ds9.mtv(subtractRes.subtractedExposure, frame=lsstDebug.frame, title=
"Subtracted image")
707 mi = subtractRes.subtractedExposure.getMaskedImage()
708 x0, y0 = mi.getX0(), mi.getY0()
709 with ds9.Buffering():
711 x, y = s.getX() - x0, s.getY() - y0
712 ctype =
"red" if s.get(
"flags.negative")
else "yellow"
713 if (s.get(
"flags.pixel.interpolated.center")
or s.get(
"flags.pixel.saturated.center")
or
714 s.get(
"flags.pixel.cr.center")):
716 elif (s.get(
"flags.pixel.interpolated.any")
or s.get(
"flags.pixel.saturated.any")
or
717 s.get(
"flags.pixel.cr.any")):
721 ds9.dot(ptype, x, y, size=4, frame=lsstDebug.frame, ctype=ctype)
724 if display
and showPixelResiduals
and selectSources:
725 nonKernelSources = []
726 for source
in selectSources:
727 if source
not in kernelSources:
728 nonKernelSources.append(source)
730 diUtils.plotPixelResiduals(exposure,
731 subtractRes.warpedExposure,
732 subtractRes.subtractedExposure,
733 subtractRes.kernelCellSet,
734 subtractRes.psfMatchingKernel,
735 subtractRes.backgroundModel,
737 self.subtract.config.kernel.active.detectionConfig,
739 diUtils.plotPixelResiduals(exposure,
740 subtractRes.warpedExposure,
741 subtractRes.subtractedExposure,
742 subtractRes.kernelCellSet,
743 subtractRes.psfMatchingKernel,
744 subtractRes.backgroundModel,
746 self.subtract.config.kernel.active.detectionConfig,
748 if display
and showDiaSources:
749 flagChecker = SourceFlagChecker(diaSources)
750 isFlagged = [flagChecker(x)
for x
in diaSources]
751 isDipole = [x.get(
"classification.dipole")
for x
in diaSources]
752 diUtils.showDiaSources(diaSources, subtractRes.subtractedExposure, isFlagged, isDipole,
753 frame=lsstDebug.frame)
756 if display
and showDipoles:
757 DipoleAnalysis().displayDipoles(subtractRes.subtractedExposure, diaSources,
758 frame=lsstDebug.frame)
761 def _getConfigName(self):
762 """Return the name of the config dataset
764 return "%sDiff_config" % (self.config.coaddName,)
766 def _getMetadataName(self):
767 """Return the name of the metadata dataset
769 return "%sDiff_metadata" % (self.config.coaddName,)
772 """Return a dict of empty catalogs for each catalog dataset produced by this task."""
773 diaSrc = afwTable.SourceCatalog(self.
schema)
775 return {self.config.coaddName +
"Diff_diaSrc": diaSrc}
778 def _makeArgumentParser(cls):
779 """Create an argument parser
781 parser = pipeBase.ArgumentParser(name=cls._DefaultName)
782 parser.add_id_argument(
"--id",
"calexp", help=
"data ID, e.g. --id visit=12345 ccd=1,2")
783 parser.add_id_argument(
"--templateId",
"calexp", doMakeDataRefList=
True,
784 help=
"Optional template data ID (visit only), e.g. --templateId visit=6789")
789 winter2013WcsShift = pexConfig.Field(dtype=float, default=0.0,
790 doc=
"Shift stars going into RegisterTask by this amount")
791 winter2013WcsRms = pexConfig.Field(dtype=float, default=0.0,
792 doc=
"Perturb stars going into RegisterTask by this amount")
795 ImageDifferenceConfig.setDefaults(self)
796 self.getTemplate.retarget(GetCalexpAsTemplateTask)
800 """!Image difference Task used in the Winter 2013 data challege.
801 Enables testing the effects of registration shifts and scatter.
803 For use with winter 2013 simulated images:
804 Use --templateId visit=88868666 for sparse data
805 --templateId visit=22222200 for dense data (g)
806 --templateId visit=11111100 for dense data (i)
808 ConfigClass = Winter2013ImageDifferenceConfig
809 _DefaultName =
"winter2013ImageDifference"
812 ImageDifferenceTask.__init__(self, **kwargs)
815 """Fit the relative astrometry between templateSources and selectSources"""
816 if self.config.winter2013WcsShift > 0.0:
817 offset = afwGeom.Extent2D(self.config.winter2013WcsShift,
818 self.config.winter2013WcsShift)
819 cKey = templateSources[0].getTable().getCentroidKey()
820 for source
in templateSources:
821 centroid = source.get(cKey)
822 source.set(cKey, centroid+offset)
823 elif self.config.winter2013WcsRms > 0.0:
824 cKey = templateSources[0].getTable().getCentroidKey()
825 for source
in templateSources:
826 offset = afwGeom.Extent2D(self.config.winter2013WcsRms*numpy.random.normal(),
827 self.config.winter2013WcsRms*numpy.random.normal())
828 centroid = source.get(cKey)
829 source.set(cKey, centroid+offset)
831 results = self.register.run(templateSources, templateExposure.getWcs(),
832 templateExposure.getBBox(), selectSources)
Image difference Task used in the Winter 2013 data challege.
def __init__
Construct an ImageDifference Task.