22 from __future__
import absolute_import, division, print_function
23 from builtins
import zip
35 from lsst.meas.extensions.astrometryNet
import LoadAstrometryNetObjectsTask
38 ObjectSizeStarSelectorTask
40 SourceFlagChecker, KernelCandidateF, makeKernelBasisList, \
41 KernelCandidateQa, DiaCatalogSourceSelectorTask, DiaCatalogSourceSelectorConfig, \
42 GetCoaddAsTemplateTask, GetCalexpAsTemplateTask, DipoleFitTask, \
43 DecorrelateALKernelSpatialTask, subtractAlgorithmRegistry
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" 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 subtractRes = self.subtract.subtractExposures(templateExposure, exposure,
325 spatiallyVarying=self.config.doSpatiallyVarying,
326 doPreConvolve=self.config.doPreConvolve)
327 subtractedExposure = subtractRes.subtractedExposure
329 elif self.config.subtract.name ==
'al':
331 scienceSigmaOrig = sciencePsf.computeShape().getDeterminantRadius()
334 templateSigma = templateExposure.getPsf().computeShape().getDeterminantRadius()
341 if self.config.doPreConvolve:
342 convControl = afwMath.ConvolutionControl()
344 srcMI = exposure.getMaskedImage()
345 destMI = srcMI.Factory(srcMI.getDimensions())
347 if self.config.useGaussianForPreConvolution:
349 kWidth, kHeight = sciencePsf.getLocalKernel().getDimensions()
354 afwMath.convolve(destMI, srcMI, preConvPsf.getLocalKernel(), convControl)
355 exposure.setMaskedImage(destMI)
356 scienceSigmaPost = scienceSigmaOrig * math.sqrt(2)
358 scienceSigmaPost = scienceSigmaOrig
361 if self.config.doSelectSources:
362 if not sensorRef.datasetExists(
"src"):
363 self.log.warn(
"Src product does not exist; running detection, measurement, selection")
365 selectSources = self.subtract.getSelectSources(
367 sigma=scienceSigmaPost,
368 doSmooth=
not self.doPreConvolve,
372 self.log.info(
"Source selection via src product")
374 selectSources = sensorRef.get(
"src")
377 nparam = len(makeKernelBasisList(self.subtract.config.kernel.active,
378 referenceFwhmPix=scienceSigmaPost * FwhmPerSigma,
379 targetFwhmPix=templateSigma * FwhmPerSigma))
381 if self.config.doAddMetrics:
383 kcQa = KernelCandidateQa(nparam)
384 selectSources = kcQa.addToSchema(selectSources)
386 if self.config.kernelSourcesFromRef:
388 astromRet = self.astrometer.loadAndMatch(exposure=exposure, sourceCat=selectSources)
389 matches = astromRet.matches
390 elif templateSources:
392 mc = afwTable.MatchControl()
393 mc.findOnlyClosest =
False 394 matches = afwTable.matchRaDec(templateSources, selectSources, 1.0*afwGeom.arcseconds,
397 raise RuntimeError(
"doSelectSources=True and kernelSourcesFromRef=False," +
398 "but template sources not available. Cannot match science " +
399 "sources with template sources. Run process* on data from " +
400 "which templates are built.")
402 kernelSources = self.sourceSelector.selectStars(exposure, selectSources,
403 matches=matches).starCat
405 random.shuffle(kernelSources, random.random)
406 controlSources = kernelSources[::self.config.controlStepSize]
407 kernelSources = [k
for i, k
in enumerate(kernelSources)
408 if i % self.config.controlStepSize]
410 if self.config.doSelectDcrCatalog:
411 redSelector = DiaCatalogSourceSelectorTask(
412 DiaCatalogSourceSelectorConfig(grMin=self.sourceSelector.config.grMax,
414 redSources = redSelector.selectStars(exposure, selectSources, matches=matches).starCat
415 controlSources.extend(redSources)
417 blueSelector = DiaCatalogSourceSelectorTask(
418 DiaCatalogSourceSelectorConfig(grMin=-99.999,
419 grMax=self.sourceSelector.config.grMin))
420 blueSources = blueSelector.selectStars(exposure, selectSources,
421 matches=matches).starCat
422 controlSources.extend(blueSources)
424 if self.config.doSelectVariableCatalog:
425 varSelector = DiaCatalogSourceSelectorTask(
426 DiaCatalogSourceSelectorConfig(includeVariable=
True))
427 varSources = varSelector.selectStars(exposure, selectSources, matches=matches).starCat
428 controlSources.extend(varSources)
430 self.log.info(
"Selected %d / %d sources for Psf matching (%d for control sample)" 431 % (len(kernelSources), len(selectSources), len(controlSources)))
433 if self.config.doUseRegister:
434 self.log.info(
"Registering images")
436 if templateSources
is None:
439 templateSources = self.subtract.getSelectSources(
448 wcsResults = self.
fitAstrometry(templateSources, templateExposure, selectSources)
449 warpedExp = self.register.warpExposure(templateExposure, wcsResults.wcs,
450 exposure.getWcs(), exposure.getBBox())
451 templateExposure = warpedExp
456 if self.config.doDebugRegister:
458 srcToMatch = {x.second.getId(): x.first
for x
in matches}
460 refCoordKey = wcsResults.matches[0].first.getTable().getCoordKey()
461 inCentroidKey = wcsResults.matches[0].second.getTable().getCentroidKey()
462 sids = [m.first.getId()
for m
in wcsResults.matches]
463 positions = [m.first.get(refCoordKey)
for m
in wcsResults.matches]
464 residuals = [m.first.get(refCoordKey).getOffsetFrom(wcsResults.wcs.pixelToSky(
465 m.second.get(inCentroidKey)))
for m
in wcsResults.matches]
466 allresids = dict(zip(sids, zip(positions, residuals)))
468 cresiduals = [m.first.get(refCoordKey).getTangentPlaneOffset(
469 wcsResults.wcs.pixelToSky(
470 m.second.get(inCentroidKey)))
for m
in wcsResults.matches]
471 colors = numpy.array([-2.5*numpy.log10(srcToMatch[x].get(
"g")) +
472 2.5*numpy.log10(srcToMatch[x].get(
"r")) 473 for x
in sids
if x
in srcToMatch.keys()])
474 dlong = numpy.array([r[0].asArcseconds()
for s, r
in zip(sids, cresiduals)
475 if s
in srcToMatch.keys()])
476 dlat = numpy.array([r[1].asArcseconds()
for s, r
in zip(sids, cresiduals)
477 if s
in srcToMatch.keys()])
478 idx1 = numpy.where(colors < self.sourceSelector.config.grMin)
479 idx2 = numpy.where((colors >= self.sourceSelector.config.grMin) &
480 (colors <= self.sourceSelector.config.grMax))
481 idx3 = numpy.where(colors > self.sourceSelector.config.grMax)
482 rms1Long = IqrToSigma * \
483 (numpy.percentile(dlong[idx1], 75)-numpy.percentile(dlong[idx1], 25))
484 rms1Lat = IqrToSigma*(numpy.percentile(dlat[idx1], 75) -
485 numpy.percentile(dlat[idx1], 25))
486 rms2Long = IqrToSigma * \
487 (numpy.percentile(dlong[idx2], 75)-numpy.percentile(dlong[idx2], 25))
488 rms2Lat = IqrToSigma*(numpy.percentile(dlat[idx2], 75) -
489 numpy.percentile(dlat[idx2], 25))
490 rms3Long = IqrToSigma * \
491 (numpy.percentile(dlong[idx3], 75)-numpy.percentile(dlong[idx3], 25))
492 rms3Lat = IqrToSigma*(numpy.percentile(dlat[idx3], 75) -
493 numpy.percentile(dlat[idx3], 25))
494 self.log.info(
"Blue star offsets'': %.3f %.3f, %.3f %.3f" %
495 (numpy.median(dlong[idx1]), rms1Long,
496 numpy.median(dlat[idx1]), rms1Lat))
497 self.log.info(
"Green star offsets'': %.3f %.3f, %.3f %.3f" %
498 (numpy.median(dlong[idx2]), rms2Long,
499 numpy.median(dlat[idx2]), rms2Lat))
500 self.log.info(
"Red star offsets'': %.3f %.3f, %.3f %.3f" %
501 (numpy.median(dlong[idx3]), rms3Long,
502 numpy.median(dlat[idx3]), rms3Lat))
504 self.metadata.add(
"RegisterBlueLongOffsetMedian", numpy.median(dlong[idx1]))
505 self.metadata.add(
"RegisterGreenLongOffsetMedian", numpy.median(dlong[idx2]))
506 self.metadata.add(
"RegisterRedLongOffsetMedian", numpy.median(dlong[idx3]))
507 self.metadata.add(
"RegisterBlueLongOffsetStd", rms1Long)
508 self.metadata.add(
"RegisterGreenLongOffsetStd", rms2Long)
509 self.metadata.add(
"RegisterRedLongOffsetStd", rms3Long)
511 self.metadata.add(
"RegisterBlueLatOffsetMedian", numpy.median(dlat[idx1]))
512 self.metadata.add(
"RegisterGreenLatOffsetMedian", numpy.median(dlat[idx2]))
513 self.metadata.add(
"RegisterRedLatOffsetMedian", numpy.median(dlat[idx3]))
514 self.metadata.add(
"RegisterBlueLatOffsetStd", rms1Lat)
515 self.metadata.add(
"RegisterGreenLatOffsetStd", rms2Lat)
516 self.metadata.add(
"RegisterRedLatOffsetStd", rms3Lat)
523 self.log.info(
"Subtracting images")
524 subtractRes = self.subtract.subtractExposures(
525 templateExposure=templateExposure,
526 scienceExposure=exposure,
527 candidateList=kernelSources,
528 convolveTemplate=self.config.convolveTemplate,
529 doWarping=
not self.config.doUseRegister
531 subtractedExposure = subtractRes.subtractedExposure
533 if self.config.doWriteMatchedExp:
534 sensorRef.put(subtractRes.matchedExposure, self.config.coaddName +
"Diff_matchedExp")
536 if self.config.doDetection:
537 self.log.info(
"Computing diffim PSF")
538 if subtractedExposure
is None:
539 subtractedExposure = sensorRef.get(subtractedExposureName)
542 if not subtractedExposure.hasPsf():
543 if self.config.convolveTemplate:
544 subtractedExposure.setPsf(exposure.getPsf())
546 if templateExposure
is None:
547 template = self.getTemplate.
run(exposure, sensorRef,
548 templateIdList=templateIdList)
549 subtractedExposure.setPsf(template.exposure.getPsf())
554 if self.config.doDecorrelation
and self.config.doSubtract:
556 if preConvPsf
is not None:
557 preConvKernel = preConvPsf.getLocalKernel()
558 decorrResult = self.decorrelate.
run(exposure, templateExposure,
560 subtractRes.psfMatchingKernel,
561 spatiallyVarying=self.config.doSpatiallyVarying,
562 preConvKernel=preConvKernel)
563 subtractedExposure = decorrResult.correctedExposure
567 if self.config.doDetection:
568 self.log.info(
"Running diaSource detection")
570 mask = subtractedExposure.getMaskedImage().getMask()
571 mask &= ~(mask.getPlaneBitMask(
"DETECTED") | mask.getPlaneBitMask(
"DETECTED_NEGATIVE"))
573 table = afwTable.SourceTable.make(self.
schema, idFactory)
575 results = self.detection.makeSourceCatalog(
577 exposure=subtractedExposure,
578 doSmooth=
not self.config.doPreConvolve
581 if self.config.doMerge:
582 fpSet = results.fpSets.positive
583 fpSet.merge(results.fpSets.negative, self.config.growFootprint,
584 self.config.growFootprint,
False)
585 diaSources = afwTable.SourceCatalog(table)
586 fpSet.makeSources(diaSources)
587 self.log.info(
"Merging detections into %d sources" % (len(diaSources)))
589 diaSources = results.sources
591 if self.config.doMeasurement:
592 newDipoleFitting = self.config.doDipoleFitting
593 self.log.info(
"Running diaSource measurement: newDipoleFitting=%r", newDipoleFitting)
594 if not newDipoleFitting:
596 self.measurement.
run(diaSources, subtractedExposure)
599 if self.config.doSubtract
and 'matchedExposure' in subtractRes.getDict():
600 self.measurement.
run(diaSources, subtractedExposure, exposure,
601 subtractRes.matchedExposure)
603 self.measurement.
run(diaSources, subtractedExposure, exposure)
606 if self.config.doMatchSources:
607 if sensorRef.datasetExists(
"src"):
609 matchRadAsec = self.config.diaSourceMatchRadius
610 matchRadPixel = matchRadAsec / exposure.getWcs().pixelScale().asArcseconds()
612 srcMatches = afwTable.matchXy(sensorRef.get(
"src"), diaSources, matchRadPixel)
613 srcMatchDict = dict([(srcMatch.second.getId(), srcMatch.first.getId())
for 614 srcMatch
in srcMatches])
615 self.log.info(
"Matched %d / %d diaSources to sources" % (len(srcMatchDict),
618 self.log.warn(
"Src product does not exist; cannot match with diaSources")
622 refAstromConfig = AstrometryConfig()
623 refAstromConfig.matcher.maxMatchDistArcSec = matchRadAsec
624 refAstrometer = AstrometryTask(refAstromConfig)
625 astromRet = refAstrometer.run(exposure=exposure, sourceCat=diaSources)
626 refMatches = astromRet.matches
627 if refMatches
is None:
628 self.log.warn(
"No diaSource matches with reference catalog")
631 self.log.info(
"Matched %d / %d diaSources to reference catalog" % (len(refMatches),
633 refMatchDict = dict([(refMatch.second.getId(), refMatch.first.getId())
for 634 refMatch
in refMatches])
637 for diaSource
in diaSources:
638 sid = diaSource.getId()
639 if sid
in srcMatchDict:
640 diaSource.set(
"srcMatchId", srcMatchDict[sid])
641 if sid
in refMatchDict:
642 diaSource.set(
"refMatchId", refMatchDict[sid])
644 if diaSources
is not None and self.config.doWriteSources:
645 sensorRef.put(diaSources, self.config.coaddName +
"Diff_diaSrc")
647 if self.config.doAddMetrics
and self.config.doSelectSources:
648 self.log.info(
"Evaluating metrics and control sample")
651 for cell
in subtractRes.kernelCellSet.getCellList():
652 for cand
in cell.begin(
False):
653 kernelCandList.append(cand)
656 basisList = kernelCandList[0].getKernel(KernelCandidateF.ORIG).getKernelList()
659 diffimTools.sourceTableToCandidateList(controlSources,
660 subtractRes.warpedExposure, exposure,
661 self.config.subtract.kernel.active,
662 self.config.subtract.kernel.active.detectionConfig,
663 self.log, doBuild=
True, basisList=basisList)
665 kcQa.apply(kernelCandList, subtractRes.psfMatchingKernel, subtractRes.backgroundModel,
667 kcQa.apply(controlCandList, subtractRes.psfMatchingKernel, subtractRes.backgroundModel)
669 if self.config.doDetection:
670 kcQa.aggregate(selectSources, self.metadata, allresids, diaSources)
672 kcQa.aggregate(selectSources, self.metadata, allresids)
674 sensorRef.put(selectSources, self.config.coaddName +
"Diff_kernelSrc")
676 if self.config.doWriteSubtractedExp:
677 sensorRef.put(subtractedExposure, subtractedExposureName)
679 self.
runDebug(exposure, subtractRes, selectSources, kernelSources, diaSources)
680 return pipeBase.Struct(
681 subtractedExposure=subtractedExposure,
682 subtractRes=subtractRes,
687 """Fit the relative astrometry between templateSources and selectSources 689 @todo remove this method. It originally fit a new WCS to the template before calling register.run 690 because our TAN-SIP fitter behaved badly for points far from CRPIX, but that's been fixed. 691 It remains because a subtask overrides it. 693 results = self.register.
run(templateSources, templateExposure.getWcs(),
694 templateExposure.getBBox(), selectSources)
697 def runDebug(self, exposure, subtractRes, selectSources, kernelSources, diaSources):
698 """@todo Test and update for current debug display and slot names 709 if not maskTransparency:
711 ds9.setMaskTransparency(maskTransparency)
713 if display
and showSubtracted:
714 ds9.mtv(subtractRes.subtractedExposure, frame=lsstDebug.frame, title=
"Subtracted image")
715 mi = subtractRes.subtractedExposure.getMaskedImage()
716 x0, y0 = mi.getX0(), mi.getY0()
717 with ds9.Buffering():
719 x, y = s.getX() - x0, s.getY() - y0
720 ctype =
"red" if s.get(
"flags.negative")
else "yellow" 721 if (s.get(
"flags.pixel.interpolated.center")
or s.get(
"flags.pixel.saturated.center")
or 722 s.get(
"flags.pixel.cr.center")):
724 elif (s.get(
"flags.pixel.interpolated.any")
or s.get(
"flags.pixel.saturated.any")
or 725 s.get(
"flags.pixel.cr.any")):
729 ds9.dot(ptype, x, y, size=4, frame=lsstDebug.frame, ctype=ctype)
732 if display
and showPixelResiduals
and selectSources:
733 nonKernelSources = []
734 for source
in selectSources:
735 if source
not in kernelSources:
736 nonKernelSources.append(source)
738 diUtils.plotPixelResiduals(exposure,
739 subtractRes.warpedExposure,
740 subtractRes.subtractedExposure,
741 subtractRes.kernelCellSet,
742 subtractRes.psfMatchingKernel,
743 subtractRes.backgroundModel,
745 self.subtract.config.kernel.active.detectionConfig,
747 diUtils.plotPixelResiduals(exposure,
748 subtractRes.warpedExposure,
749 subtractRes.subtractedExposure,
750 subtractRes.kernelCellSet,
751 subtractRes.psfMatchingKernel,
752 subtractRes.backgroundModel,
754 self.subtract.config.kernel.active.detectionConfig,
756 if display
and showDiaSources:
757 flagChecker = SourceFlagChecker(diaSources)
758 isFlagged = [flagChecker(x)
for x
in diaSources]
759 isDipole = [x.get(
"classification.dipole")
for x
in diaSources]
760 diUtils.showDiaSources(diaSources, subtractRes.subtractedExposure, isFlagged, isDipole,
761 frame=lsstDebug.frame)
764 if display
and showDipoles:
765 DipoleAnalysis().displayDipoles(subtractRes.subtractedExposure, diaSources,
766 frame=lsstDebug.frame)
769 def _getConfigName(self):
770 """Return the name of the config dataset 772 return "%sDiff_config" % (self.config.coaddName,)
774 def _getMetadataName(self):
775 """Return the name of the metadata dataset 777 return "%sDiff_metadata" % (self.config.coaddName,)
780 """Return a dict of empty catalogs for each catalog dataset produced by this task.""" 781 diaSrc = afwTable.SourceCatalog(self.
schema)
783 return {self.config.coaddName +
"Diff_diaSrc": diaSrc}
786 def _makeArgumentParser(cls):
787 """Create an argument parser 790 parser.add_id_argument(
"--id",
"calexp", help=
"data ID, e.g. --id visit=12345 ccd=1,2")
791 parser.add_id_argument(
"--templateId",
"calexp", doMakeDataRefList=
True,
792 help=
"Optional template data ID (visit only), e.g. --templateId visit=6789")
797 winter2013WcsShift = pexConfig.Field(dtype=float, default=0.0,
798 doc=
"Shift stars going into RegisterTask by this amount")
799 winter2013WcsRms = pexConfig.Field(dtype=float, default=0.0,
800 doc=
"Perturb stars going into RegisterTask by this amount")
803 ImageDifferenceConfig.setDefaults(self)
808 """!Image difference Task used in the Winter 2013 data challege. 809 Enables testing the effects of registration shifts and scatter. 811 For use with winter 2013 simulated images: 812 Use --templateId visit=88868666 for sparse data 813 --templateId visit=22222200 for dense data (g) 814 --templateId visit=11111100 for dense data (i) 816 ConfigClass = Winter2013ImageDifferenceConfig
817 _DefaultName =
"winter2013ImageDifference" 820 ImageDifferenceTask.__init__(self, **kwargs)
823 """Fit the relative astrometry between templateSources and selectSources""" 824 if self.config.winter2013WcsShift > 0.0:
825 offset = afwGeom.Extent2D(self.config.winter2013WcsShift,
826 self.config.winter2013WcsShift)
827 cKey = templateSources[0].getTable().getCentroidKey()
828 for source
in templateSources:
829 centroid = source.get(cKey)
830 source.set(cKey, centroid+offset)
831 elif self.config.winter2013WcsRms > 0.0:
832 cKey = templateSources[0].getTable().getCentroidKey()
833 for source
in templateSources:
834 offset = afwGeom.Extent2D(self.config.winter2013WcsRms*numpy.random.normal(),
835 self.config.winter2013WcsRms*numpy.random.normal())
836 centroid = source.get(cKey)
837 source.set(cKey, centroid+offset)
839 results = self.register.
run(templateSources, templateExposure.getWcs(),
840 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)