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, PsfAttributes, 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")
173 self.subtract.kernel.name =
"AL"
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"
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(
"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 ctr = afwGeom.Box2D(exposure.getBBox()).getCenter()
324 psfAttr = PsfAttributes(sciencePsf, afwGeom.Point2I(ctr))
325 scienceSigmaOrig = psfAttr.computeGaussianWidth(psfAttr.ADAPTIVE_MOMENT)
328 ctr = afwGeom.Box2D(templateExposure.getBBox()).getCenter()
329 psfAttr = PsfAttributes(templateExposure.getPsf(), afwGeom.Point2I(ctr))
330 templateSigma = psfAttr.computeGaussianWidth(psfAttr.ADAPTIVE_MOMENT)
336 if self.config.doPreConvolve:
337 convControl = afwMath.ConvolutionControl()
339 srcMI = exposure.getMaskedImage()
340 destMI = srcMI.Factory(srcMI.getDimensions())
342 if self.config.useGaussianForPreConvolution:
344 kWidth, kHeight = sciencePsf.getLocalKernel().getDimensions()
345 preConvPsf = SingleGaussianPsf(kWidth, kHeight, scienceSigmaOrig)
349 afwMath.convolve(destMI, srcMI, preConvPsf.getLocalKernel(), convControl)
350 exposure.setMaskedImage(destMI)
351 scienceSigmaPost = scienceSigmaOrig * math.sqrt(2)
353 scienceSigmaPost = scienceSigmaOrig
356 if self.config.doSelectSources:
357 if not sensorRef.datasetExists(
"src"):
358 self.log.warn(
"Src product does not exist; running detection, measurement, selection")
360 selectSources = self.subtract.getSelectSources(
362 sigma=scienceSigmaPost,
363 doSmooth=
not self.doPreConvolve,
367 self.log.info(
"Source selection via src product")
369 selectSources = sensorRef.get(
"src")
372 nparam = len(makeKernelBasisList(self.subtract.config.kernel.active,
373 referenceFwhmPix=scienceSigmaPost * FwhmPerSigma,
374 targetFwhmPix=templateSigma * FwhmPerSigma))
376 if self.config.doAddMetrics:
378 kcQa = KernelCandidateQa(nparam)
379 selectSources = kcQa.addToSchema(selectSources)
381 if self.config.kernelSourcesFromRef:
383 astromRet = self.astrometer.loadAndMatch(exposure=exposure, sourceCat=selectSources)
384 matches = astromRet.matches
385 elif templateSources:
387 mc = afwTable.MatchControl()
388 mc.findOnlyClosest =
False
389 matches = afwTable.matchRaDec(templateSources, selectSources, 1.0*afwGeom.arcseconds,
392 raise RuntimeError(
"doSelectSources=True and kernelSourcesFromRef=False," +
393 "but template sources not available. Cannot match science " +
394 "sources with template sources. Run process* on data from " +
395 "which templates are built.")
397 kernelSources = self.sourceSelector.selectStars(exposure, selectSources,
398 matches=matches).starCat
400 random.shuffle(kernelSources, random.random)
401 controlSources = kernelSources[::self.config.controlStepSize]
402 kernelSources = [k
for i, k
in enumerate(kernelSources)
if i % self.config.controlStepSize]
404 if self.config.doSelectDcrCatalog:
405 redSelector = DiaCatalogSourceSelectorTask(
406 DiaCatalogSourceSelectorConfig(grMin=self.sourceSelector.config.grMax, grMax=99.999))
407 redSources = redSelector.selectStars(exposure, selectSources, matches=matches).starCat
408 controlSources.extend(redSources)
410 blueSelector = DiaCatalogSourceSelectorTask(
411 DiaCatalogSourceSelectorConfig(grMin=-99.999, grMax=self.sourceSelector.config.grMin))
412 blueSources = blueSelector.selectStars(exposure, selectSources, matches=matches).starCat
413 controlSources.extend(blueSources)
415 if self.config.doSelectVariableCatalog:
416 varSelector = DiaCatalogSourceSelectorTask(
417 DiaCatalogSourceSelectorConfig(includeVariable=
True))
418 varSources = varSelector.selectStars(exposure, selectSources, matches=matches).starCat
419 controlSources.extend(varSources)
421 self.log.info(
"Selected %d / %d sources for Psf matching (%d for control sample)"
422 % (len(kernelSources), len(selectSources), len(controlSources)))
424 if self.config.doUseRegister:
425 self.log.info(
"Registering images")
427 if templateSources
is None:
430 templateSources = self.subtract.getSelectSources(
439 wcsResults = self.
fitAstrometry(templateSources, templateExposure, selectSources)
440 warpedExp = self.register.warpExposure(templateExposure, wcsResults.wcs,
441 exposure.getWcs(), exposure.getBBox())
442 templateExposure = warpedExp
447 if self.config.doDebugRegister:
449 srcToMatch = {x.second.getId(): x.first
for x
in matches}
451 refCoordKey = wcsResults.matches[0].first.getTable().getCoordKey()
452 inCentroidKey = wcsResults.matches[0].second.getTable().getCentroidKey()
453 sids = [m.first.getId()
for m
in wcsResults.matches]
454 positions = [m.first.get(refCoordKey)
for m
in wcsResults.matches]
455 residuals = [m.first.get(refCoordKey).getOffsetFrom(wcsResults.wcs.pixelToSky(
456 m.second.get(inCentroidKey)))
for m
in wcsResults.matches]
457 allresids = dict(zip(sids, zip(positions, residuals)))
459 cresiduals = [m.first.get(refCoordKey).getTangentPlaneOffset(
460 wcsResults.wcs.pixelToSky(
461 m.second.get(inCentroidKey)))
for m
in wcsResults.matches]
462 colors = numpy.array([-2.5*numpy.log10(srcToMatch[x].get(
"g")) +
463 2.5*numpy.log10(srcToMatch[x].get(
"r"))
464 for x
in sids
if x
in srcToMatch.keys()])
465 dlong = numpy.array([r[0].asArcseconds()
for s, r
in zip(sids, cresiduals)
466 if s
in srcToMatch.keys()])
467 dlat = numpy.array([r[1].asArcseconds()
for s, r
in zip(sids, cresiduals)
468 if s
in srcToMatch.keys()])
469 idx1 = numpy.where(colors < self.sourceSelector.config.grMin)
470 idx2 = numpy.where((colors >= self.sourceSelector.config.grMin) &
471 (colors <= self.sourceSelector.config.grMax))
472 idx3 = numpy.where(colors > self.sourceSelector.config.grMax)
473 rms1Long = IqrToSigma * \
474 (numpy.percentile(dlong[idx1], 75)-numpy.percentile(dlong[idx1], 25))
475 rms1Lat = IqrToSigma*(numpy.percentile(dlat[idx1], 75)-numpy.percentile(dlat[idx1], 25))
476 rms2Long = IqrToSigma * \
477 (numpy.percentile(dlong[idx2], 75)-numpy.percentile(dlong[idx2], 25))
478 rms2Lat = IqrToSigma*(numpy.percentile(dlat[idx2], 75)-numpy.percentile(dlat[idx2], 25))
479 rms3Long = IqrToSigma * \
480 (numpy.percentile(dlong[idx3], 75)-numpy.percentile(dlong[idx3], 25))
481 rms3Lat = IqrToSigma*(numpy.percentile(dlat[idx3], 75)-numpy.percentile(dlat[idx3], 25))
482 self.log.info(
"Blue star offsets'': %.3f %.3f, %.3f %.3f" % (numpy.median(dlong[idx1]),
484 numpy.median(dlat[idx1]),
486 self.log.info(
"Green star offsets'': %.3f %.3f, %.3f %.3f" % (numpy.median(dlong[idx2]),
488 numpy.median(dlat[idx2]),
490 self.log.info(
"Red star offsets'': %.3f %.3f, %.3f %.3f" % (numpy.median(dlong[idx3]),
492 numpy.median(dlat[idx3]),
495 self.metadata.add(
"RegisterBlueLongOffsetMedian", numpy.median(dlong[idx1]))
496 self.metadata.add(
"RegisterGreenLongOffsetMedian", numpy.median(dlong[idx2]))
497 self.metadata.add(
"RegisterRedLongOffsetMedian", numpy.median(dlong[idx3]))
498 self.metadata.add(
"RegisterBlueLongOffsetStd", rms1Long)
499 self.metadata.add(
"RegisterGreenLongOffsetStd", rms2Long)
500 self.metadata.add(
"RegisterRedLongOffsetStd", rms3Long)
502 self.metadata.add(
"RegisterBlueLatOffsetMedian", numpy.median(dlat[idx1]))
503 self.metadata.add(
"RegisterGreenLatOffsetMedian", numpy.median(dlat[idx2]))
504 self.metadata.add(
"RegisterRedLatOffsetMedian", numpy.median(dlat[idx3]))
505 self.metadata.add(
"RegisterBlueLatOffsetStd", rms1Lat)
506 self.metadata.add(
"RegisterGreenLatOffsetStd", rms2Lat)
507 self.metadata.add(
"RegisterRedLatOffsetStd", rms3Lat)
514 self.log.info(
"Subtracting images")
515 subtractRes = self.subtract.subtractExposures(
516 templateExposure=templateExposure,
517 scienceExposure=exposure,
518 candidateList=kernelSources,
519 convolveTemplate=self.config.convolveTemplate,
520 doWarping=
not self.config.doUseRegister
522 subtractedExposure = subtractRes.subtractedExposure
524 if self.config.doWriteMatchedExp:
525 sensorRef.put(subtractRes.matchedExposure, self.config.coaddName +
"Diff_matchedExp")
527 if self.config.doDetection:
528 self.log.info(
"Computing diffim PSF")
529 if subtractedExposure
is None:
530 subtractedExposure = sensorRef.get(subtractedExposureName)
533 if not subtractedExposure.hasPsf():
534 if self.config.convolveTemplate:
535 subtractedExposure.setPsf(exposure.getPsf())
537 if templateExposure
is None:
538 template = self.getTemplate.run(exposure, sensorRef, templateIdList=templateIdList)
539 subtractedExposure.setPsf(template.exposure.getPsf())
543 if self.config.doDecorrelation
and self.config.doSubtract:
544 decorrResult = self.decorrelate.run(exposure, templateExposure,
546 subtractRes.psfMatchingKernel)
547 subtractedExposure = decorrResult.correctedExposure
549 if self.config.doDetection:
550 self.log.info(
"Running diaSource detection")
552 mask = subtractedExposure.getMaskedImage().getMask()
553 mask &= ~(mask.getPlaneBitMask(
"DETECTED") | mask.getPlaneBitMask(
"DETECTED_NEGATIVE"))
555 table = afwTable.SourceTable.make(self.
schema, idFactory)
557 results = self.detection.makeSourceCatalog(
559 exposure=subtractedExposure,
560 doSmooth=
not self.config.doPreConvolve
563 if self.config.doMerge:
564 fpSet = results.fpSets.positive
565 fpSet.merge(results.fpSets.negative, self.config.growFootprint,
566 self.config.growFootprint,
False)
567 diaSources = afwTable.SourceCatalog(table)
568 fpSet.makeSources(diaSources)
569 self.log.info(
"Merging detections into %d sources" % (len(diaSources)))
571 diaSources = results.sources
573 if self.config.doMeasurement:
574 self.log.info(
"Running diaSource measurement")
575 if not self.config.doDipoleFitting:
576 self.measurement.run(diaSources, subtractedExposure)
578 if self.config.doSubtract:
579 self.measurement.run(diaSources, subtractedExposure, exposure,
580 subtractRes.matchedExposure)
582 self.measurement.run(diaSources, subtractedExposure, exposure)
585 if self.config.doMatchSources:
586 if sensorRef.datasetExists(
"src"):
588 matchRadAsec = self.config.diaSourceMatchRadius
589 matchRadPixel = matchRadAsec / exposure.getWcs().pixelScale().asArcseconds()
591 srcMatches = afwTable.matchXy(sensorRef.get(
"src"), diaSources, matchRadPixel)
592 srcMatchDict = dict([(srcMatch.second.getId(), srcMatch.first.getId())
for
593 srcMatch
in srcMatches])
594 self.log.info(
"Matched %d / %d diaSources to sources" % (len(srcMatchDict),
597 self.log.warn(
"Src product does not exist; cannot match with diaSources")
601 refAstromConfig = AstrometryConfig()
602 refAstromConfig.matcher.maxMatchDistArcSec = matchRadAsec
603 refAstrometer = AstrometryTask(refAstromConfig)
604 astromRet = refAstrometer.run(exposure=exposure, sourceCat=diaSources)
605 refMatches = astromRet.matches
606 if refMatches
is None:
607 self.log.warn(
"No diaSource matches with reference catalog")
610 self.log.info(
"Matched %d / %d diaSources to reference catalog" % (len(refMatches),
612 refMatchDict = dict([(refMatch.second.getId(), refMatch.first.getId())
for
613 refMatch
in refMatches])
616 for diaSource
in diaSources:
617 sid = diaSource.getId()
618 if sid
in srcMatchDict:
619 diaSource.set(
"srcMatchId", srcMatchDict[sid])
620 if sid
in refMatchDict:
621 diaSource.set(
"refMatchId", refMatchDict[sid])
623 if diaSources
is not None and self.config.doWriteSources:
624 sensorRef.put(diaSources, self.config.coaddName +
"Diff_diaSrc")
626 if self.config.doAddMetrics
and self.config.doSelectSources:
627 self.log.info(
"Evaluating metrics and control sample")
630 for cell
in subtractRes.kernelCellSet.getCellList():
631 for cand
in cell.begin(
False):
632 kernelCandList.append(cand)
635 basisList = kernelCandList[0].getKernel(KernelCandidateF.ORIG).getKernelList()
638 diffimTools.sourceTableToCandidateList(controlSources,
639 subtractRes.warpedExposure, exposure,
640 self.config.subtract.kernel.active,
641 self.config.subtract.kernel.active.detectionConfig,
642 self.log, doBuild=
True, basisList=basisList)
644 kcQa.apply(kernelCandList, subtractRes.psfMatchingKernel, subtractRes.backgroundModel,
646 kcQa.apply(controlCandList, subtractRes.psfMatchingKernel, subtractRes.backgroundModel)
648 if self.config.doDetection:
649 kcQa.aggregate(selectSources, self.metadata, allresids, diaSources)
651 kcQa.aggregate(selectSources, self.metadata, allresids)
653 sensorRef.put(selectSources, self.config.coaddName +
"Diff_kernelSrc")
655 if self.config.doWriteSubtractedExp:
656 sensorRef.put(subtractedExposure, subtractedExposureName)
658 self.
runDebug(exposure, subtractRes, selectSources, kernelSources, diaSources)
659 return pipeBase.Struct(
660 subtractedExposure=subtractedExposure,
661 subtractRes=subtractRes,
666 """Fit the relative astrometry between templateSources and selectSources
668 @todo remove this method. It originally fit a new WCS to the template before calling register.run
669 because our TAN-SIP fitter behaved badly for points far from CRPIX, but that's been fixed.
670 It remains because a subtask overrides it.
672 results = self.register.run(templateSources, templateExposure.getWcs(),
673 templateExposure.getBBox(), selectSources)
676 def runDebug(self, exposure, subtractRes, selectSources, kernelSources, diaSources):
677 """@todo Test and update for current debug display and slot names
680 display = lsstDebug.Info(__name__).display
681 showSubtracted = lsstDebug.Info(__name__).showSubtracted
682 showPixelResiduals = lsstDebug.Info(__name__).showPixelResiduals
683 showDiaSources = lsstDebug.Info(__name__).showDiaSources
684 showDipoles = lsstDebug.Info(__name__).showDipoles
685 maskTransparency = lsstDebug.Info(__name__).maskTransparency
687 import lsst.afw.display.ds9
as ds9
688 if not maskTransparency:
690 ds9.setMaskTransparency(maskTransparency)
692 if display
and showSubtracted:
693 ds9.mtv(subtractRes.subtractedExposure, frame=lsstDebug.frame, title=
"Subtracted image")
694 mi = subtractRes.subtractedExposure.getMaskedImage()
695 x0, y0 = mi.getX0(), mi.getY0()
696 with ds9.Buffering():
698 x, y = s.getX() - x0, s.getY() - y0
699 ctype =
"red" if s.get(
"flags.negative")
else "yellow"
700 if (s.get(
"flags.pixel.interpolated.center")
or s.get(
"flags.pixel.saturated.center")
or
701 s.get(
"flags.pixel.cr.center")):
703 elif (s.get(
"flags.pixel.interpolated.any")
or s.get(
"flags.pixel.saturated.any")
or
704 s.get(
"flags.pixel.cr.any")):
708 ds9.dot(ptype, x, y, size=4, frame=lsstDebug.frame, ctype=ctype)
711 if display
and showPixelResiduals
and selectSources:
712 nonKernelSources = []
713 for source
in selectSources:
714 if source
not in kernelSources:
715 nonKernelSources.append(source)
717 diUtils.plotPixelResiduals(exposure,
718 subtractRes.warpedExposure,
719 subtractRes.subtractedExposure,
720 subtractRes.kernelCellSet,
721 subtractRes.psfMatchingKernel,
722 subtractRes.backgroundModel,
724 self.subtract.config.kernel.active.detectionConfig,
726 diUtils.plotPixelResiduals(exposure,
727 subtractRes.warpedExposure,
728 subtractRes.subtractedExposure,
729 subtractRes.kernelCellSet,
730 subtractRes.psfMatchingKernel,
731 subtractRes.backgroundModel,
733 self.subtract.config.kernel.active.detectionConfig,
735 if display
and showDiaSources:
736 flagChecker = SourceFlagChecker(diaSources)
737 isFlagged = [flagChecker(x)
for x
in diaSources]
738 isDipole = [x.get(
"classification.dipole")
for x
in diaSources]
739 diUtils.showDiaSources(diaSources, subtractRes.subtractedExposure, isFlagged, isDipole,
740 frame=lsstDebug.frame)
743 if display
and showDipoles:
744 DipoleAnalysis().displayDipoles(subtractRes.subtractedExposure, diaSources,
745 frame=lsstDebug.frame)
748 def _getConfigName(self):
749 """Return the name of the config dataset
751 return "%sDiff_config" % (self.config.coaddName,)
753 def _getMetadataName(self):
754 """Return the name of the metadata dataset
756 return "%sDiff_metadata" % (self.config.coaddName,)
759 """Return a dict of empty catalogs for each catalog dataset produced by this task."""
760 diaSrc = afwTable.SourceCatalog(self.
schema)
762 return {self.config.coaddName +
"Diff_diaSrc": diaSrc}
765 def _makeArgumentParser(cls):
766 """Create an argument parser
768 parser = pipeBase.ArgumentParser(name=cls._DefaultName)
769 parser.add_id_argument(
"--id",
"calexp", help=
"data ID, e.g. --id visit=12345 ccd=1,2")
770 parser.add_id_argument(
"--templateId",
"calexp", doMakeDataRefList=
True,
771 help=
"Optional template data ID (visit only), e.g. --templateId visit=6789")
776 winter2013WcsShift = pexConfig.Field(dtype=float, default=0.0,
777 doc=
"Shift stars going into RegisterTask by this amount")
778 winter2013WcsRms = pexConfig.Field(dtype=float, default=0.0,
779 doc=
"Perturb stars going into RegisterTask by this amount")
782 ImageDifferenceConfig.setDefaults(self)
783 self.getTemplate.retarget(GetCalexpAsTemplateTask)
787 """!Image difference Task used in the Winter 2013 data challege.
788 Enables testing the effects of registration shifts and scatter.
790 For use with winter 2013 simulated images:
791 Use --templateId visit=88868666 for sparse data
792 --templateId visit=22222200 for dense data (g)
793 --templateId visit=11111100 for dense data (i)
795 ConfigClass = Winter2013ImageDifferenceConfig
796 _DefaultName =
"winter2013ImageDifference"
799 ImageDifferenceTask.__init__(self, **kwargs)
802 """Fit the relative astrometry between templateSources and selectSources"""
803 if self.config.winter2013WcsShift > 0.0:
804 offset = afwGeom.Extent2D(self.config.winter2013WcsShift,
805 self.config.winter2013WcsShift)
806 cKey = templateSources[0].getTable().getCentroidKey()
807 for source
in templateSources:
808 centroid = source.get(cKey)
809 source.set(cKey, centroid+offset)
810 elif self.config.winter2013WcsRms > 0.0:
811 cKey = templateSources[0].getTable().getCentroidKey()
812 for source
in templateSources:
813 offset = afwGeom.Extent2D(self.config.winter2013WcsRms*numpy.random.normal(),
814 self.config.winter2013WcsRms*numpy.random.normal())
815 centroid = source.get(cKey)
816 source.set(cKey, centroid+offset)
818 results = self.register.run(templateSources, templateExposure.getWcs(),
819 templateExposure.getBBox(), selectSources)
Image difference Task used in the Winter 2013 data challege.
def __init__
Construct an ImageDifference Task.