34 from lsst.meas.extensions.astrometryNet
import LoadAstrometryNetObjectsTask
37 ObjectSizeStarSelectorTask
39 SourceFlagChecker, KernelCandidateF, makeKernelBasisList, \
40 KernelCandidateQa, DiaCatalogSourceSelectorTask, DiaCatalogSourceSelectorConfig, \
41 GetCoaddAsTemplateTask, GetCalexpAsTemplateTask, DipoleFitTask, \
42 DecorrelateALKernelSpatialTask, subtractAlgorithmRegistry
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 doForcedMeasurement = pexConfig.Field(dtype=bool, default=
True,
86 doc=
"Force photometer diaSource locations on PVI?")
87 doWriteSubtractedExp = pexConfig.Field(dtype=bool, default=
True, doc=
"Write difference exposure?")
88 doWriteMatchedExp = pexConfig.Field(dtype=bool, default=
False,
89 doc=
"Write warped and PSF-matched template coadd exposure?")
90 doWriteSources = pexConfig.Field(dtype=bool, default=
True, doc=
"Write sources?")
91 doAddMetrics = pexConfig.Field(dtype=bool, default=
True,
92 doc=
"Add columns to the source table to hold analysis metrics?")
94 coaddName = pexConfig.Field(
95 doc=
"coadd name: typically one of deep, goodSeeing, or dcr",
99 convolveTemplate = pexConfig.Field(
100 doc=
"Which image gets convolved (default = template)",
104 refObjLoader = pexConfig.ConfigurableField(
105 target=LoadAstrometryNetObjectsTask,
106 doc=
"reference object loader",
108 astrometer = pexConfig.ConfigurableField(
109 target=AstrometryTask,
110 doc=
"astrometry task; used to match sources to reference objects, but not to fit a WCS",
112 sourceSelector = pexConfig.ConfigurableField(
113 target=ObjectSizeStarSelectorTask,
114 doc=
"Source selection algorithm",
116 subtract = subtractAlgorithmRegistry.makeField(
"Subtraction Algorithm", default=
"al")
117 decorrelate = pexConfig.ConfigurableField(
118 target=DecorrelateALKernelSpatialTask,
119 doc=
"Decorrelate effects of A&L kernel convolution on image difference, only if doSubtract is True. " 120 "If this option is enabled, then detection.thresholdValue should be set to 5.0 (rather than the " 123 doSpatiallyVarying = pexConfig.Field(
126 doc=
"If using Zogy or A&L decorrelation, perform these on a grid across the " 127 "image in order to allow for spatial variations" 129 detection = pexConfig.ConfigurableField(
130 target=SourceDetectionTask,
131 doc=
"Low-threshold detection for final measurement",
133 measurement = pexConfig.ConfigurableField(
134 target=DipoleFitTask,
135 doc=
"Enable updated dipole fitting method",
137 forcedMeasurement = pexConfig.ConfigurableField(
138 target=ForcedMeasurementTask,
139 doc=
"Subtask to force photometer PVI at diaSource location.",
141 getTemplate = pexConfig.ConfigurableField(
142 target=GetCoaddAsTemplateTask,
143 doc=
"Subtask to retrieve template exposure and sources",
145 controlStepSize = pexConfig.Field(
146 doc=
"What step size (every Nth one) to select a control sample from the kernelSources",
150 controlRandomSeed = pexConfig.Field(
151 doc=
"Random seed for shuffing the control sample",
155 register = pexConfig.ConfigurableField(
157 doc=
"Task to enable image-to-image image registration (warping)",
159 kernelSourcesFromRef = pexConfig.Field(
160 doc=
"Select sources to measure kernel from reference catalog if True, template if false",
164 templateSipOrder = pexConfig.Field(dtype=int, default=2,
165 doc=
"Sip Order for fitting the Template Wcs " 166 "(default is too high, overfitting)")
168 growFootprint = pexConfig.Field(dtype=int, default=2,
169 doc=
"Grow positive and negative footprints by this amount before merging")
171 diaSourceMatchRadius = pexConfig.Field(dtype=float, default=0.5,
172 doc=
"Match radius (in arcseconds) " 173 "for DiaSource to Source association")
178 self.
subtract[
'al'].kernel.name =
"AL" 179 self.
subtract[
'al'].kernel.active.fitForBackground =
True 180 self.
subtract[
'al'].kernel.active.spatialKernelOrder = 1
181 self.
subtract[
'al'].kernel.active.spatialBgOrder = 0
188 self.
detection.thresholdPolarity =
"both" 190 self.
detection.reEstimateBackground =
False 191 self.
detection.thresholdType =
"pixel_stdev" 197 self.
measurement.algorithms.names.add(
'base_PeakLikelihoodFlux')
201 "id":
"objectId",
"parent":
"parentObjectId",
"coord_ra":
"coord_ra",
"coord_dec":
"coord_dec"}
209 pexConfig.Config.validate(self)
211 raise ValueError(
"Cannot run source measurement without source detection.")
213 raise ValueError(
"Cannot run source merging without source detection.")
215 raise ValueError(
"doUseRegister=True and doSelectSources=False. " +
216 "Cannot run RegisterTask without selecting sources.")
219 raise ValueError(
"Mis-matched coaddName and getTemplate.coaddName in the config.")
226 return pipeBase.TaskRunner.getTargetList(parsedCmd, templateIdList=parsedCmd.templateId.idList,
231 """Subtract an image from a template and measure the result 233 ConfigClass = ImageDifferenceConfig
234 RunnerClass = ImageDifferenceTaskRunner
235 _DefaultName =
"imageDifference" 238 """!Construct an ImageDifference Task 240 @param[in] butler Butler object to use in constructing reference object loaders 242 pipeBase.CmdLineTask.__init__(self, **kwargs)
243 self.makeSubtask(
"getTemplate")
245 self.makeSubtask(
"subtract")
247 if self.config.subtract.name ==
'al' and self.config.doDecorrelation:
248 self.makeSubtask(
"decorrelate")
250 if self.config.doUseRegister:
251 self.makeSubtask(
"register")
252 self.
schema = afwTable.SourceTable.makeMinimalSchema()
254 if self.config.doSelectSources:
255 self.makeSubtask(
"sourceSelector")
256 self.makeSubtask(
'refObjLoader', butler=butler)
257 self.makeSubtask(
"astrometer", refObjLoader=self.refObjLoader)
260 if self.config.doDetection:
261 self.makeSubtask(
"detection", schema=self.
schema)
262 if self.config.doMeasurement:
263 self.makeSubtask(
"measurement", schema=self.
schema,
265 if self.config.doForcedMeasurement:
267 "totFlux",
"D",
"Forced flux measured on the PVI")
269 "totFluxErr",
"D",
"Forced flux error measured on the PVI")
270 self.makeSubtask(
"forcedMeasurement", refSchema=self.
schema)
271 if self.config.doMatchSources:
272 self.
schema.addField(
"refMatchId",
"L",
"unique id of reference catalog match")
273 self.
schema.addField(
"srcMatchId",
"L",
"unique id of source match")
277 """Subtract an image from a template coadd and measure the result 280 - warp template coadd to match WCS of image 281 - PSF match image to warped template 282 - subtract image from PSF-matched, warped template 283 - persist difference image 287 @param sensorRef: sensor-level butler data reference, used for the following data products: 293 - self.config.coaddName + "Coadd_skyMap" 294 - self.config.coaddName + "Coadd" 295 Input or output, depending on config: 296 - self.config.coaddName + "Diff_subtractedExp" 297 Output, depending on config: 298 - self.config.coaddName + "Diff_matchedExp" 299 - self.config.coaddName + "Diff_src" 301 @return pipe_base Struct containing these fields: 302 - subtractedExposure: exposure after subtracting template; 303 the unpersisted version if subtraction not run but detection run 304 None if neither subtraction nor detection run (i.e. nothing useful done) 305 - subtractRes: results of subtraction task; None if subtraction not run 306 - sources: detected and possibly measured sources; None if detection not run 308 self.log.info(
"Processing %s" % (sensorRef.dataId))
311 subtractedExposure =
None 315 controlSources =
None 321 expBits = sensorRef.get(
"ccdExposureId_bits")
322 expId = int(sensorRef.get(
"ccdExposureId"))
323 idFactory = afwTable.IdFactory.makeSource(expId, 64 - expBits)
326 exposure = sensorRef.get(
"calexp", immediate=
True)
327 if self.config.doAddCalexpBackground:
328 mi = exposure.getMaskedImage()
329 mi += sensorRef.get(
"calexpBackground").getImage()
330 if not exposure.hasPsf():
331 raise pipeBase.TaskError(
"Exposure has no psf")
332 sciencePsf = exposure.getPsf()
334 subtractedExposureName = self.config.coaddName +
"Diff_differenceExp" 335 templateExposure =
None 336 templateSources =
None 337 if self.config.doSubtract:
338 template = self.getTemplate.run(exposure, sensorRef, templateIdList=templateIdList)
339 templateExposure = template.exposure
340 templateSources = template.sources
342 if self.config.subtract.name ==
'zogy':
343 subtractRes = self.subtract.subtractExposures(templateExposure, exposure,
345 spatiallyVarying=self.config.doSpatiallyVarying,
346 doPreConvolve=self.config.doPreConvolve)
347 subtractedExposure = subtractRes.subtractedExposure
349 elif self.config.subtract.name ==
'al':
351 scienceSigmaOrig = sciencePsf.computeShape().getDeterminantRadius()
354 templateSigma = templateExposure.getPsf().computeShape().getDeterminantRadius()
361 if self.config.doPreConvolve:
362 convControl = afwMath.ConvolutionControl()
364 srcMI = exposure.getMaskedImage()
365 destMI = srcMI.Factory(srcMI.getDimensions())
367 if self.config.useGaussianForPreConvolution:
369 kWidth, kHeight = sciencePsf.getLocalKernel().getDimensions()
374 afwMath.convolve(destMI, srcMI, preConvPsf.getLocalKernel(), convControl)
375 exposure.setMaskedImage(destMI)
376 scienceSigmaPost = scienceSigmaOrig * math.sqrt(2)
378 scienceSigmaPost = scienceSigmaOrig
381 if self.config.doSelectSources:
382 if not sensorRef.datasetExists(
"src"):
383 self.log.warn(
"Src product does not exist; running detection, measurement, selection")
385 selectSources = self.subtract.getSelectSources(
387 sigma=scienceSigmaPost,
388 doSmooth=
not self.doPreConvolve,
392 self.log.info(
"Source selection via src product")
394 selectSources = sensorRef.get(
"src")
397 nparam = len(makeKernelBasisList(self.subtract.config.kernel.active,
398 referenceFwhmPix=scienceSigmaPost * FwhmPerSigma,
399 targetFwhmPix=templateSigma * FwhmPerSigma))
401 if self.config.doAddMetrics:
403 kcQa = KernelCandidateQa(nparam)
404 selectSources = kcQa.addToSchema(selectSources)
406 if self.config.kernelSourcesFromRef:
408 astromRet = self.astrometer.loadAndMatch(exposure=exposure, sourceCat=selectSources)
409 matches = astromRet.matches
410 elif templateSources:
412 mc = afwTable.MatchControl()
413 mc.findOnlyClosest =
False 414 matches = afwTable.matchRaDec(templateSources, selectSources, 1.0*afwGeom.arcseconds,
417 raise RuntimeError(
"doSelectSources=True and kernelSourcesFromRef=False," +
418 "but template sources not available. Cannot match science " +
419 "sources with template sources. Run process* on data from " +
420 "which templates are built.")
422 kernelSources = self.sourceSelector.run(selectSources, exposure=exposure,
423 matches=matches).sourceCat
425 random.shuffle(kernelSources, random.random)
426 controlSources = kernelSources[::self.config.controlStepSize]
427 kernelSources = [k
for i, k
in enumerate(kernelSources)
428 if i % self.config.controlStepSize]
430 if self.config.doSelectDcrCatalog:
431 redSelector = DiaCatalogSourceSelectorTask(
432 DiaCatalogSourceSelectorConfig(grMin=self.sourceSelector.config.grMax,
434 redSources = redSelector.selectStars(exposure, selectSources, matches=matches).starCat
435 controlSources.extend(redSources)
437 blueSelector = DiaCatalogSourceSelectorTask(
438 DiaCatalogSourceSelectorConfig(grMin=-99.999,
439 grMax=self.sourceSelector.config.grMin))
440 blueSources = blueSelector.selectStars(exposure, selectSources,
441 matches=matches).starCat
442 controlSources.extend(blueSources)
444 if self.config.doSelectVariableCatalog:
445 varSelector = DiaCatalogSourceSelectorTask(
446 DiaCatalogSourceSelectorConfig(includeVariable=
True))
447 varSources = varSelector.selectStars(exposure, selectSources, matches=matches).starCat
448 controlSources.extend(varSources)
450 self.log.info(
"Selected %d / %d sources for Psf matching (%d for control sample)" 451 % (len(kernelSources), len(selectSources), len(controlSources)))
453 if self.config.doUseRegister:
454 self.log.info(
"Registering images")
456 if templateSources
is None:
459 templateSources = self.subtract.getSelectSources(
468 wcsResults = self.
fitAstrometry(templateSources, templateExposure, selectSources)
469 warpedExp = self.register.warpExposure(templateExposure, wcsResults.wcs,
470 exposure.getWcs(), exposure.getBBox())
471 templateExposure = warpedExp
476 if self.config.doDebugRegister:
478 srcToMatch = {x.second.getId(): x.first
for x
in matches}
480 refCoordKey = wcsResults.matches[0].first.getTable().getCoordKey()
481 inCentroidKey = wcsResults.matches[0].second.getTable().getCentroidKey()
482 sids = [m.first.getId()
for m
in wcsResults.matches]
483 positions = [m.first.get(refCoordKey)
for m
in wcsResults.matches]
484 residuals = [m.first.get(refCoordKey).getOffsetFrom(wcsResults.wcs.pixelToSky(
485 m.second.get(inCentroidKey)))
for m
in wcsResults.matches]
486 allresids = dict(zip(sids, zip(positions, residuals)))
488 cresiduals = [m.first.get(refCoordKey).getTangentPlaneOffset(
489 wcsResults.wcs.pixelToSky(
490 m.second.get(inCentroidKey)))
for m
in wcsResults.matches]
491 colors = numpy.array([-2.5*numpy.log10(srcToMatch[x].get(
"g")) +
492 2.5*numpy.log10(srcToMatch[x].get(
"r")) 493 for x
in sids
if x
in srcToMatch.keys()])
494 dlong = numpy.array([r[0].asArcseconds()
for s, r
in zip(sids, cresiduals)
495 if s
in srcToMatch.keys()])
496 dlat = numpy.array([r[1].asArcseconds()
for s, r
in zip(sids, cresiduals)
497 if s
in srcToMatch.keys()])
498 idx1 = numpy.where(colors < self.sourceSelector.config.grMin)
499 idx2 = numpy.where((colors >= self.sourceSelector.config.grMin) &
500 (colors <= self.sourceSelector.config.grMax))
501 idx3 = numpy.where(colors > self.sourceSelector.config.grMax)
502 rms1Long = IqrToSigma * \
503 (numpy.percentile(dlong[idx1], 75)-numpy.percentile(dlong[idx1], 25))
504 rms1Lat = IqrToSigma*(numpy.percentile(dlat[idx1], 75) -
505 numpy.percentile(dlat[idx1], 25))
506 rms2Long = IqrToSigma * \
507 (numpy.percentile(dlong[idx2], 75)-numpy.percentile(dlong[idx2], 25))
508 rms2Lat = IqrToSigma*(numpy.percentile(dlat[idx2], 75) -
509 numpy.percentile(dlat[idx2], 25))
510 rms3Long = IqrToSigma * \
511 (numpy.percentile(dlong[idx3], 75)-numpy.percentile(dlong[idx3], 25))
512 rms3Lat = IqrToSigma*(numpy.percentile(dlat[idx3], 75) -
513 numpy.percentile(dlat[idx3], 25))
514 self.log.info(
"Blue star offsets'': %.3f %.3f, %.3f %.3f" %
515 (numpy.median(dlong[idx1]), rms1Long,
516 numpy.median(dlat[idx1]), rms1Lat))
517 self.log.info(
"Green star offsets'': %.3f %.3f, %.3f %.3f" %
518 (numpy.median(dlong[idx2]), rms2Long,
519 numpy.median(dlat[idx2]), rms2Lat))
520 self.log.info(
"Red star offsets'': %.3f %.3f, %.3f %.3f" %
521 (numpy.median(dlong[idx3]), rms3Long,
522 numpy.median(dlat[idx3]), rms3Lat))
524 self.metadata.add(
"RegisterBlueLongOffsetMedian", numpy.median(dlong[idx1]))
525 self.metadata.add(
"RegisterGreenLongOffsetMedian", numpy.median(dlong[idx2]))
526 self.metadata.add(
"RegisterRedLongOffsetMedian", numpy.median(dlong[idx3]))
527 self.metadata.add(
"RegisterBlueLongOffsetStd", rms1Long)
528 self.metadata.add(
"RegisterGreenLongOffsetStd", rms2Long)
529 self.metadata.add(
"RegisterRedLongOffsetStd", rms3Long)
531 self.metadata.add(
"RegisterBlueLatOffsetMedian", numpy.median(dlat[idx1]))
532 self.metadata.add(
"RegisterGreenLatOffsetMedian", numpy.median(dlat[idx2]))
533 self.metadata.add(
"RegisterRedLatOffsetMedian", numpy.median(dlat[idx3]))
534 self.metadata.add(
"RegisterBlueLatOffsetStd", rms1Lat)
535 self.metadata.add(
"RegisterGreenLatOffsetStd", rms2Lat)
536 self.metadata.add(
"RegisterRedLatOffsetStd", rms3Lat)
543 self.log.info(
"Subtracting images")
544 subtractRes = self.subtract.subtractExposures(
545 templateExposure=templateExposure,
546 scienceExposure=exposure,
547 candidateList=kernelSources,
548 convolveTemplate=self.config.convolveTemplate,
549 doWarping=
not self.config.doUseRegister
551 subtractedExposure = subtractRes.subtractedExposure
553 if self.config.doWriteMatchedExp:
554 sensorRef.put(subtractRes.matchedExposure, self.config.coaddName +
"Diff_matchedExp")
556 if self.config.doDetection:
557 self.log.info(
"Computing diffim PSF")
558 if subtractedExposure
is None:
559 subtractedExposure = sensorRef.get(subtractedExposureName)
562 if not subtractedExposure.hasPsf():
563 if self.config.convolveTemplate:
564 subtractedExposure.setPsf(exposure.getPsf())
566 if templateExposure
is None:
567 template = self.getTemplate.run(exposure, sensorRef,
568 templateIdList=templateIdList)
569 subtractedExposure.setPsf(template.exposure.getPsf())
574 if self.config.doDecorrelation
and self.config.doSubtract:
576 if preConvPsf
is not None:
577 preConvKernel = preConvPsf.getLocalKernel()
578 decorrResult = self.decorrelate.run(exposure, templateExposure,
580 subtractRes.psfMatchingKernel,
581 spatiallyVarying=self.config.doSpatiallyVarying,
582 preConvKernel=preConvKernel)
583 subtractedExposure = decorrResult.correctedExposure
587 if self.config.doDetection:
588 self.log.info(
"Running diaSource detection")
590 mask = subtractedExposure.getMaskedImage().getMask()
591 mask &= ~(mask.getPlaneBitMask(
"DETECTED") | mask.getPlaneBitMask(
"DETECTED_NEGATIVE"))
593 table = afwTable.SourceTable.make(self.
schema, idFactory)
595 results = self.detection.makeSourceCatalog(
597 exposure=subtractedExposure,
598 doSmooth=
not self.config.doPreConvolve
601 if self.config.doMerge:
602 fpSet = results.fpSets.positive
603 fpSet.merge(results.fpSets.negative, self.config.growFootprint,
604 self.config.growFootprint,
False)
605 diaSources = afwTable.SourceCatalog(table)
606 fpSet.makeSources(diaSources)
607 self.log.info(
"Merging detections into %d sources" % (len(diaSources)))
609 diaSources = results.sources
611 if self.config.doMeasurement:
612 newDipoleFitting = self.config.doDipoleFitting
613 self.log.info(
"Running diaSource measurement: newDipoleFitting=%r", newDipoleFitting)
614 if not newDipoleFitting:
616 self.measurement.run(diaSources, subtractedExposure)
619 if self.config.doSubtract
and 'matchedExposure' in subtractRes.getDict():
620 self.measurement.run(diaSources, subtractedExposure, exposure,
621 subtractRes.matchedExposure)
623 self.measurement.run(diaSources, subtractedExposure, exposure)
625 if self.config.doForcedMeasurement:
628 forcedSources = self.forcedMeasurement.generateMeasCat(
629 exposure, diaSources, subtractedExposure.getWcs())
630 self.forcedMeasurement.run(forcedSources, exposure, diaSources, subtractedExposure.getWcs())
631 mapper = afwTable.SchemaMapper(forcedSources.schema, diaSources.schema)
632 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_instFlux")[0],
"totFlux",
True)
633 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_instFluxErr")[0],
635 for diaSource, forcedSource
in zip(diaSources, forcedSources):
636 diaSource.assign(forcedSource, mapper)
639 if self.config.doMatchSources:
640 if sensorRef.datasetExists(
"src"):
642 matchRadAsec = self.config.diaSourceMatchRadius
643 matchRadPixel = matchRadAsec / exposure.getWcs().pixelScale().asArcseconds()
645 srcMatches = afwTable.matchXy(sensorRef.get(
"src"), diaSources, matchRadPixel)
646 srcMatchDict = dict([(srcMatch.second.getId(), srcMatch.first.getId())
for 647 srcMatch
in srcMatches])
648 self.log.info(
"Matched %d / %d diaSources to sources" % (len(srcMatchDict),
651 self.log.warn(
"Src product does not exist; cannot match with diaSources")
655 refAstromConfig = AstrometryConfig()
656 refAstromConfig.matcher.maxMatchDistArcSec = matchRadAsec
657 refAstrometer = AstrometryTask(refAstromConfig)
658 astromRet = refAstrometer.run(exposure=exposure, sourceCat=diaSources)
659 refMatches = astromRet.matches
660 if refMatches
is None:
661 self.log.warn(
"No diaSource matches with reference catalog")
664 self.log.info(
"Matched %d / %d diaSources to reference catalog" % (len(refMatches),
666 refMatchDict = dict([(refMatch.second.getId(), refMatch.first.getId())
for 667 refMatch
in refMatches])
670 for diaSource
in diaSources:
671 sid = diaSource.getId()
672 if sid
in srcMatchDict:
673 diaSource.set(
"srcMatchId", srcMatchDict[sid])
674 if sid
in refMatchDict:
675 diaSource.set(
"refMatchId", refMatchDict[sid])
677 if diaSources
is not None and self.config.doWriteSources:
678 sensorRef.put(diaSources, self.config.coaddName +
"Diff_diaSrc")
680 if self.config.doAddMetrics
and self.config.doSelectSources:
681 self.log.info(
"Evaluating metrics and control sample")
684 for cell
in subtractRes.kernelCellSet.getCellList():
685 for cand
in cell.begin(
False):
686 kernelCandList.append(cand)
689 basisList = kernelCandList[0].getKernel(KernelCandidateF.ORIG).getKernelList()
692 diffimTools.sourceTableToCandidateList(controlSources,
693 subtractRes.warpedExposure, exposure,
694 self.config.subtract.kernel.active,
695 self.config.subtract.kernel.active.detectionConfig,
696 self.log, doBuild=
True, basisList=basisList)
698 kcQa.apply(kernelCandList, subtractRes.psfMatchingKernel, subtractRes.backgroundModel,
700 kcQa.apply(controlCandList, subtractRes.psfMatchingKernel, subtractRes.backgroundModel)
702 if self.config.doDetection:
703 kcQa.aggregate(selectSources, self.metadata, allresids, diaSources)
705 kcQa.aggregate(selectSources, self.metadata, allresids)
707 sensorRef.put(selectSources, self.config.coaddName +
"Diff_kernelSrc")
709 if self.config.doWriteSubtractedExp:
710 sensorRef.put(subtractedExposure, subtractedExposureName)
712 self.
runDebug(exposure, subtractRes, selectSources, kernelSources, diaSources)
713 return pipeBase.Struct(
714 subtractedExposure=subtractedExposure,
715 subtractRes=subtractRes,
720 """Fit the relative astrometry between templateSources and selectSources 722 @todo remove this method. It originally fit a new WCS to the template before calling register.run 723 because our TAN-SIP fitter behaved badly for points far from CRPIX, but that's been fixed. 724 It remains because a subtask overrides it. 726 results = self.register.run(templateSources, templateExposure.getWcs(),
727 templateExposure.getBBox(), selectSources)
730 def runDebug(self, exposure, subtractRes, selectSources, kernelSources, diaSources):
731 """@todo Test and update for current debug display and slot names 742 if not maskTransparency:
744 ds9.setMaskTransparency(maskTransparency)
746 if display
and showSubtracted:
747 ds9.mtv(subtractRes.subtractedExposure, frame=lsstDebug.frame, title=
"Subtracted image")
748 mi = subtractRes.subtractedExposure.getMaskedImage()
749 x0, y0 = mi.getX0(), mi.getY0()
750 with ds9.Buffering():
752 x, y = s.getX() - x0, s.getY() - y0
753 ctype =
"red" if s.get(
"flags.negative")
else "yellow" 754 if (s.get(
"flags.pixel.interpolated.center")
or s.get(
"flags.pixel.saturated.center")
or 755 s.get(
"flags.pixel.cr.center")):
757 elif (s.get(
"flags.pixel.interpolated.any")
or s.get(
"flags.pixel.saturated.any")
or 758 s.get(
"flags.pixel.cr.any")):
762 ds9.dot(ptype, x, y, size=4, frame=lsstDebug.frame, ctype=ctype)
765 if display
and showPixelResiduals
and selectSources:
766 nonKernelSources = []
767 for source
in selectSources:
768 if source
not in kernelSources:
769 nonKernelSources.append(source)
771 diUtils.plotPixelResiduals(exposure,
772 subtractRes.warpedExposure,
773 subtractRes.subtractedExposure,
774 subtractRes.kernelCellSet,
775 subtractRes.psfMatchingKernel,
776 subtractRes.backgroundModel,
778 self.subtract.config.kernel.active.detectionConfig,
780 diUtils.plotPixelResiduals(exposure,
781 subtractRes.warpedExposure,
782 subtractRes.subtractedExposure,
783 subtractRes.kernelCellSet,
784 subtractRes.psfMatchingKernel,
785 subtractRes.backgroundModel,
787 self.subtract.config.kernel.active.detectionConfig,
789 if display
and showDiaSources:
790 flagChecker = SourceFlagChecker(diaSources)
791 isFlagged = [flagChecker(x)
for x
in diaSources]
792 isDipole = [x.get(
"classification.dipole")
for x
in diaSources]
793 diUtils.showDiaSources(diaSources, subtractRes.subtractedExposure, isFlagged, isDipole,
794 frame=lsstDebug.frame)
797 if display
and showDipoles:
798 DipoleAnalysis().displayDipoles(subtractRes.subtractedExposure, diaSources,
799 frame=lsstDebug.frame)
802 def _getConfigName(self):
803 """Return the name of the config dataset 805 return "%sDiff_config" % (self.config.coaddName,)
807 def _getMetadataName(self):
808 """Return the name of the metadata dataset 810 return "%sDiff_metadata" % (self.config.coaddName,)
813 """Return a dict of empty catalogs for each catalog dataset produced by this task.""" 814 diaSrc = afwTable.SourceCatalog(self.
schema)
816 return {self.config.coaddName +
"Diff_diaSrc": diaSrc}
819 def _makeArgumentParser(cls):
820 """Create an argument parser 823 parser.add_id_argument(
"--id",
"calexp", help=
"data ID, e.g. --id visit=12345 ccd=1,2")
824 parser.add_id_argument(
"--templateId",
"calexp", doMakeDataRefList=
True,
825 help=
"Optional template data ID (visit only), e.g. --templateId visit=6789")
830 winter2013WcsShift = pexConfig.Field(dtype=float, default=0.0,
831 doc=
"Shift stars going into RegisterTask by this amount")
832 winter2013WcsRms = pexConfig.Field(dtype=float, default=0.0,
833 doc=
"Perturb stars going into RegisterTask by this amount")
836 ImageDifferenceConfig.setDefaults(self)
841 """!Image difference Task used in the Winter 2013 data challege. 842 Enables testing the effects of registration shifts and scatter. 844 For use with winter 2013 simulated images: 845 Use --templateId visit=88868666 for sparse data 846 --templateId visit=22222200 for dense data (g) 847 --templateId visit=11111100 for dense data (i) 849 ConfigClass = Winter2013ImageDifferenceConfig
850 _DefaultName =
"winter2013ImageDifference" 853 ImageDifferenceTask.__init__(self, **kwargs)
856 """Fit the relative astrometry between templateSources and selectSources""" 857 if self.config.winter2013WcsShift > 0.0:
858 offset = afwGeom.Extent2D(self.config.winter2013WcsShift,
859 self.config.winter2013WcsShift)
860 cKey = templateSources[0].getTable().getCentroidKey()
861 for source
in templateSources:
862 centroid = source.get(cKey)
863 source.set(cKey, centroid+offset)
864 elif self.config.winter2013WcsRms > 0.0:
865 cKey = templateSources[0].getTable().getCentroidKey()
866 for source
in templateSources:
867 offset = afwGeom.Extent2D(self.config.winter2013WcsRms*numpy.random.normal(),
868 self.config.winter2013WcsRms*numpy.random.normal())
869 centroid = source.get(cKey)
870 source.set(cKey, centroid+offset)
872 results = self.register.run(templateSources, templateExposure.getWcs(),
873 templateExposure.getBBox(), selectSources)
def __init__(self, butler=None, kwargs)
Construct an ImageDifference Task.
def fitAstrometry(self, templateSources, templateExposure, selectSources)
def runDataRef(self, sensorRef, templateIdList=None)
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)