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 "ip_diffim_forced_PsfFlux_instFlux",
"D",
268 "Forced PSF flux measured on the direct image.")
270 "ip_diffim_forced_PsfFlux_instFluxErr",
"D",
271 "Forced PSF flux error measured on the direct image.")
273 "ip_diffim_forced_PsfFlux_area",
"F",
274 "Forced PSF flux effective area of PSF.",
277 "ip_diffim_forced_PsfFlux_flag",
"Flag",
278 "Forced PSF flux general failure flag.")
280 "ip_diffim_forced_PsfFlux_flag_noGoodPixels",
"Flag",
281 "Forced PSF flux not enough non-rejected pixels in data to attempt the fit.")
283 "ip_diffim_forced_PsfFlux_flag_edge",
"Flag",
284 "Forced PSF flux object was too close to the edge of the image to use the full PSF model.")
285 self.makeSubtask(
"forcedMeasurement", refSchema=self.
schema)
286 if self.config.doMatchSources:
287 self.
schema.addField(
"refMatchId",
"L",
"unique id of reference catalog match")
288 self.
schema.addField(
"srcMatchId",
"L",
"unique id of source match")
292 """Subtract an image from a template coadd and measure the result 295 - warp template coadd to match WCS of image 296 - PSF match image to warped template 297 - subtract image from PSF-matched, warped template 298 - persist difference image 302 @param sensorRef: sensor-level butler data reference, used for the following data products: 308 - self.config.coaddName + "Coadd_skyMap" 309 - self.config.coaddName + "Coadd" 310 Input or output, depending on config: 311 - self.config.coaddName + "Diff_subtractedExp" 312 Output, depending on config: 313 - self.config.coaddName + "Diff_matchedExp" 314 - self.config.coaddName + "Diff_src" 316 @return pipe_base Struct containing these fields: 317 - subtractedExposure: exposure after subtracting template; 318 the unpersisted version if subtraction not run but detection run 319 None if neither subtraction nor detection run (i.e. nothing useful done) 320 - subtractRes: results of subtraction task; None if subtraction not run 321 - sources: detected and possibly measured sources; None if detection not run 323 self.log.
info(
"Processing %s" % (sensorRef.dataId))
326 subtractedExposure =
None 330 controlSources =
None 336 expBits = sensorRef.get(
"ccdExposureId_bits")
337 expId =
int(sensorRef.get(
"ccdExposureId"))
338 idFactory = afwTable.IdFactory.makeSource(expId, 64 - expBits)
341 exposure = sensorRef.get(
"calexp", immediate=
True)
342 if self.config.doAddCalexpBackground:
343 mi = exposure.getMaskedImage()
344 mi += sensorRef.get(
"calexpBackground").getImage()
345 if not exposure.hasPsf():
346 raise pipeBase.TaskError(
"Exposure has no psf")
347 sciencePsf = exposure.getPsf()
349 subtractedExposureName = self.config.coaddName +
"Diff_differenceExp" 350 templateExposure =
None 351 templateSources =
None 352 if self.config.doSubtract:
353 template = self.getTemplate.
run(exposure, sensorRef, templateIdList=templateIdList)
354 templateExposure = template.exposure
355 templateSources = template.sources
357 if self.config.subtract.name ==
'zogy':
358 subtractRes = self.subtract.subtractExposures(templateExposure, exposure,
360 spatiallyVarying=self.config.doSpatiallyVarying,
361 doPreConvolve=self.config.doPreConvolve)
362 subtractedExposure = subtractRes.subtractedExposure
364 elif self.config.subtract.name ==
'al':
366 scienceSigmaOrig = sciencePsf.computeShape().getDeterminantRadius()
369 templateSigma = templateExposure.getPsf().computeShape().getDeterminantRadius()
376 if self.config.doPreConvolve:
379 srcMI = exposure.getMaskedImage()
380 destMI = srcMI.Factory(srcMI.getDimensions())
382 if self.config.useGaussianForPreConvolution:
384 kWidth, kHeight = sciencePsf.getLocalKernel().getDimensions()
390 exposure.setMaskedImage(destMI)
391 scienceSigmaPost = scienceSigmaOrig * math.sqrt(2)
393 scienceSigmaPost = scienceSigmaOrig
396 if self.config.doSelectSources:
397 if not sensorRef.datasetExists(
"src"):
398 self.log.
warn(
"Src product does not exist; running detection, measurement, selection")
400 selectSources = self.subtract.getSelectSources(
402 sigma=scienceSigmaPost,
403 doSmooth=
not self.doPreConvolve,
407 self.log.
info(
"Source selection via src product")
409 selectSources = sensorRef.get(
"src")
413 referenceFwhmPix=scienceSigmaPost * FwhmPerSigma,
414 targetFwhmPix=templateSigma * FwhmPerSigma))
416 if self.config.doAddMetrics:
418 kcQa = KernelCandidateQa(nparam)
419 selectSources = kcQa.addToSchema(selectSources)
421 if self.config.kernelSourcesFromRef:
423 astromRet = self.astrometer.loadAndMatch(exposure=exposure, sourceCat=selectSources)
424 matches = astromRet.matches
425 elif templateSources:
428 mc.findOnlyClosest =
False 432 raise RuntimeError(
"doSelectSources=True and kernelSourcesFromRef=False," 433 "but template sources not available. Cannot match science " 434 "sources with template sources. Run process* on data from " 435 "which templates are built.")
437 kernelSources = self.sourceSelector.
run(selectSources, exposure=exposure,
438 matches=matches).sourceCat
440 random.shuffle(kernelSources, random.random)
441 controlSources = kernelSources[::self.config.controlStepSize]
442 kernelSources = [k
for i, k
in enumerate(kernelSources)
443 if i % self.config.controlStepSize]
445 if self.config.doSelectDcrCatalog:
446 redSelector = DiaCatalogSourceSelectorTask(
447 DiaCatalogSourceSelectorConfig(grMin=self.sourceSelector.config.grMax,
449 redSources = redSelector.selectStars(exposure, selectSources, matches=matches).starCat
450 controlSources.extend(redSources)
452 blueSelector = DiaCatalogSourceSelectorTask(
453 DiaCatalogSourceSelectorConfig(grMin=-99.999,
454 grMax=self.sourceSelector.config.grMin))
455 blueSources = blueSelector.selectStars(exposure, selectSources,
456 matches=matches).starCat
457 controlSources.extend(blueSources)
459 if self.config.doSelectVariableCatalog:
460 varSelector = DiaCatalogSourceSelectorTask(
461 DiaCatalogSourceSelectorConfig(includeVariable=
True))
462 varSources = varSelector.selectStars(exposure, selectSources, matches=matches).starCat
463 controlSources.extend(varSources)
465 self.log.
info(
"Selected %d / %d sources for Psf matching (%d for control sample)" 466 % (len(kernelSources), len(selectSources), len(controlSources)))
468 if self.config.doUseRegister:
469 self.log.
info(
"Registering images")
471 if templateSources
is None:
474 templateSources = self.subtract.getSelectSources(
483 wcsResults = self.
fitAstrometry(templateSources, templateExposure, selectSources)
484 warpedExp = self.register.
warpExposure(templateExposure, wcsResults.wcs,
485 exposure.getWcs(), exposure.getBBox())
486 templateExposure = warpedExp
491 if self.config.doDebugRegister:
493 srcToMatch = {x.second.getId(): x.first
for x
in matches}
495 refCoordKey = wcsResults.matches[0].first.getTable().getCoordKey()
496 inCentroidKey = wcsResults.matches[0].second.getTable().getCentroidKey()
497 sids = [m.first.getId()
for m
in wcsResults.matches]
498 positions = [m.first.get(refCoordKey)
for m
in wcsResults.matches]
499 residuals = [m.first.get(refCoordKey).getOffsetFrom(wcsResults.wcs.pixelToSky(
500 m.second.get(inCentroidKey)))
for m
in wcsResults.matches]
501 allresids = dict(zip(sids, zip(positions, residuals)))
503 cresiduals = [m.first.get(refCoordKey).getTangentPlaneOffset(
504 wcsResults.wcs.pixelToSky(
505 m.second.get(inCentroidKey)))
for m
in wcsResults.matches]
506 colors = numpy.array([-2.5*numpy.log10(srcToMatch[x].get(
"g")) +
507 2.5*numpy.log10(srcToMatch[x].get(
"r")) 508 for x
in sids
if x
in srcToMatch.keys()])
509 dlong = numpy.array([r[0].asArcseconds()
for s, r
in zip(sids, cresiduals)
510 if s
in srcToMatch.keys()])
511 dlat = numpy.array([r[1].asArcseconds()
for s, r
in zip(sids, cresiduals)
512 if s
in srcToMatch.keys()])
513 idx1 = numpy.where(colors < self.sourceSelector.config.grMin)
514 idx2 = numpy.where((colors >= self.sourceSelector.config.grMin) &
515 (colors <= self.sourceSelector.config.grMax))
516 idx3 = numpy.where(colors > self.sourceSelector.config.grMax)
517 rms1Long = IqrToSigma * \
518 (numpy.percentile(dlong[idx1], 75)-numpy.percentile(dlong[idx1], 25))
519 rms1Lat = IqrToSigma*(numpy.percentile(dlat[idx1], 75) -
520 numpy.percentile(dlat[idx1], 25))
521 rms2Long = IqrToSigma * \
522 (numpy.percentile(dlong[idx2], 75)-numpy.percentile(dlong[idx2], 25))
523 rms2Lat = IqrToSigma*(numpy.percentile(dlat[idx2], 75) -
524 numpy.percentile(dlat[idx2], 25))
525 rms3Long = IqrToSigma * \
526 (numpy.percentile(dlong[idx3], 75)-numpy.percentile(dlong[idx3], 25))
527 rms3Lat = IqrToSigma*(numpy.percentile(dlat[idx3], 75) -
528 numpy.percentile(dlat[idx3], 25))
529 self.log.
info(
"Blue star offsets'': %.3f %.3f, %.3f %.3f" %
530 (numpy.median(dlong[idx1]), rms1Long,
531 numpy.median(dlat[idx1]), rms1Lat))
532 self.log.
info(
"Green star offsets'': %.3f %.3f, %.3f %.3f" %
533 (numpy.median(dlong[idx2]), rms2Long,
534 numpy.median(dlat[idx2]), rms2Lat))
535 self.log.
info(
"Red star offsets'': %.3f %.3f, %.3f %.3f" %
536 (numpy.median(dlong[idx3]), rms3Long,
537 numpy.median(dlat[idx3]), rms3Lat))
539 self.metadata.add(
"RegisterBlueLongOffsetMedian", numpy.median(dlong[idx1]))
540 self.metadata.add(
"RegisterGreenLongOffsetMedian", numpy.median(dlong[idx2]))
541 self.metadata.add(
"RegisterRedLongOffsetMedian", numpy.median(dlong[idx3]))
542 self.metadata.add(
"RegisterBlueLongOffsetStd", rms1Long)
543 self.metadata.add(
"RegisterGreenLongOffsetStd", rms2Long)
544 self.metadata.add(
"RegisterRedLongOffsetStd", rms3Long)
546 self.metadata.add(
"RegisterBlueLatOffsetMedian", numpy.median(dlat[idx1]))
547 self.metadata.add(
"RegisterGreenLatOffsetMedian", numpy.median(dlat[idx2]))
548 self.metadata.add(
"RegisterRedLatOffsetMedian", numpy.median(dlat[idx3]))
549 self.metadata.add(
"RegisterBlueLatOffsetStd", rms1Lat)
550 self.metadata.add(
"RegisterGreenLatOffsetStd", rms2Lat)
551 self.metadata.add(
"RegisterRedLatOffsetStd", rms3Lat)
558 self.log.
info(
"Subtracting images")
559 subtractRes = self.subtract.subtractExposures(
560 templateExposure=templateExposure,
561 scienceExposure=exposure,
562 candidateList=kernelSources,
563 convolveTemplate=self.config.convolveTemplate,
564 doWarping=
not self.config.doUseRegister
566 subtractedExposure = subtractRes.subtractedExposure
568 if self.config.doWriteMatchedExp:
569 sensorRef.put(subtractRes.matchedExposure, self.config.coaddName +
"Diff_matchedExp")
571 if self.config.doDetection:
572 self.log.
info(
"Computing diffim PSF")
573 if subtractedExposure
is None:
574 subtractedExposure = sensorRef.get(subtractedExposureName)
577 if not subtractedExposure.hasPsf():
578 if self.config.convolveTemplate:
579 subtractedExposure.setPsf(exposure.getPsf())
581 if templateExposure
is None:
582 template = self.getTemplate.
run(exposure, sensorRef,
583 templateIdList=templateIdList)
584 subtractedExposure.setPsf(template.exposure.getPsf())
589 if self.config.doDecorrelation
and self.config.doSubtract:
591 if preConvPsf
is not None:
592 preConvKernel = preConvPsf.getLocalKernel()
593 decorrResult = self.decorrelate.
run(exposure, templateExposure,
595 subtractRes.psfMatchingKernel,
596 spatiallyVarying=self.config.doSpatiallyVarying,
597 preConvKernel=preConvKernel)
598 subtractedExposure = decorrResult.correctedExposure
602 if self.config.doDetection:
603 self.log.
info(
"Running diaSource detection")
605 mask = subtractedExposure.getMaskedImage().getMask()
606 mask &= ~(mask.getPlaneBitMask(
"DETECTED") | mask.getPlaneBitMask(
"DETECTED_NEGATIVE"))
608 table = afwTable.SourceTable.make(self.
schema, idFactory)
610 results = self.detection.makeSourceCatalog(
612 exposure=subtractedExposure,
613 doSmooth=
not self.config.doPreConvolve
616 if self.config.doMerge:
617 fpSet = results.fpSets.positive
618 fpSet.merge(results.fpSets.negative, self.config.growFootprint,
619 self.config.growFootprint,
False)
621 fpSet.makeSources(diaSources)
622 self.log.
info(
"Merging detections into %d sources" % (len(diaSources)))
624 diaSources = results.sources
626 if self.config.doMeasurement:
627 newDipoleFitting = self.config.doDipoleFitting
628 self.log.
info(
"Running diaSource measurement: newDipoleFitting=%r", newDipoleFitting)
629 if not newDipoleFitting:
631 self.measurement.
run(diaSources, subtractedExposure)
634 if self.config.doSubtract
and 'matchedExposure' in subtractRes.getDict():
635 self.measurement.
run(diaSources, subtractedExposure, exposure,
636 subtractRes.matchedExposure)
638 self.measurement.
run(diaSources, subtractedExposure, exposure)
640 if self.config.doForcedMeasurement:
643 forcedSources = self.forcedMeasurement.generateMeasCat(
644 exposure, diaSources, subtractedExposure.getWcs())
645 self.forcedMeasurement.
run(forcedSources, exposure, diaSources, subtractedExposure.getWcs())
647 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_instFlux")[0],
648 "ip_diffim_forced_PsfFlux_instFlux",
True)
649 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_instFluxErr")[0],
650 "ip_diffim_forced_PsfFlux_instFluxErr",
True)
651 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_area")[0],
652 "ip_diffim_forced_PsfFlux_area",
True)
653 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_flag")[0],
654 "ip_diffim_forced_PsfFlux_flag",
True)
655 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_flag_noGoodPixels")[0],
656 "ip_diffim_forced_PsfFlux_flag_noGoodPixels",
True)
657 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_flag_edge")[0],
658 "ip_diffim_forced_PsfFlux_flag_edge",
True)
659 for diaSource, forcedSource
in zip(diaSources, forcedSources):
660 diaSource.assign(forcedSource, mapper)
663 if self.config.doMatchSources:
664 if sensorRef.datasetExists(
"src"):
666 matchRadAsec = self.config.diaSourceMatchRadius
667 matchRadPixel = matchRadAsec / exposure.getWcs().pixelScale().asArcseconds()
669 srcMatches =
afwTable.matchXy(sensorRef.get(
"src"), diaSources, matchRadPixel)
670 srcMatchDict = dict([(srcMatch.second.getId(), srcMatch.first.getId())
for 671 srcMatch
in srcMatches])
672 self.log.
info(
"Matched %d / %d diaSources to sources" % (len(srcMatchDict),
675 self.log.
warn(
"Src product does not exist; cannot match with diaSources")
680 refAstromConfig.matcher.maxMatchDistArcSec = matchRadAsec
682 astromRet = refAstrometer.run(exposure=exposure, sourceCat=diaSources)
683 refMatches = astromRet.matches
684 if refMatches
is None:
685 self.log.
warn(
"No diaSource matches with reference catalog")
688 self.log.
info(
"Matched %d / %d diaSources to reference catalog" % (len(refMatches),
690 refMatchDict = dict([(refMatch.second.getId(), refMatch.first.getId())
for 691 refMatch
in refMatches])
694 for diaSource
in diaSources:
695 sid = diaSource.getId()
696 if sid
in srcMatchDict:
697 diaSource.set(
"srcMatchId", srcMatchDict[sid])
698 if sid
in refMatchDict:
699 diaSource.set(
"refMatchId", refMatchDict[sid])
701 if diaSources
is not None and self.config.doWriteSources:
702 sensorRef.put(diaSources, self.config.coaddName +
"Diff_diaSrc")
704 if self.config.doAddMetrics
and self.config.doSelectSources:
705 self.log.
info(
"Evaluating metrics and control sample")
708 for cell
in subtractRes.kernelCellSet.getCellList():
709 for cand
in cell.begin(
False):
710 kernelCandList.append(cand)
713 basisList = kernelCandList[0].getKernel(KernelCandidateF.ORIG).getKernelList()
716 diffimTools.sourceTableToCandidateList(controlSources,
717 subtractRes.warpedExposure, exposure,
718 self.config.subtract.kernel.active,
719 self.config.subtract.kernel.active.detectionConfig,
720 self.log, doBuild=
True, basisList=basisList)
722 kcQa.apply(kernelCandList, subtractRes.psfMatchingKernel, subtractRes.backgroundModel,
724 kcQa.apply(controlCandList, subtractRes.psfMatchingKernel, subtractRes.backgroundModel)
726 if self.config.doDetection:
727 kcQa.aggregate(selectSources, self.metadata, allresids, diaSources)
729 kcQa.aggregate(selectSources, self.metadata, allresids)
731 sensorRef.put(selectSources, self.config.coaddName +
"Diff_kernelSrc")
733 if self.config.doWriteSubtractedExp:
734 sensorRef.put(subtractedExposure, subtractedExposureName)
736 self.
runDebug(exposure, subtractRes, selectSources, kernelSources, diaSources)
737 return pipeBase.Struct(
738 subtractedExposure=subtractedExposure,
739 subtractRes=subtractRes,
744 """Fit the relative astrometry between templateSources and selectSources 746 @todo remove this method. It originally fit a new WCS to the template before calling register.run 747 because our TAN-SIP fitter behaved badly for points far from CRPIX, but that's been fixed. 748 It remains because a subtask overrides it. 750 results = self.register.
run(templateSources, templateExposure.getWcs(),
751 templateExposure.getBBox(), selectSources)
754 def runDebug(self, exposure, subtractRes, selectSources, kernelSources, diaSources):
755 """@todo Test and update for current debug display and slot names 766 if not maskTransparency:
768 ds9.setMaskTransparency(maskTransparency)
770 if display
and showSubtracted:
771 ds9.mtv(subtractRes.subtractedExposure, frame=lsstDebug.frame, title=
"Subtracted image")
772 mi = subtractRes.subtractedExposure.getMaskedImage()
773 x0, y0 = mi.getX0(), mi.getY0()
774 with ds9.Buffering():
776 x, y = s.getX() - x0, s.getY() - y0
777 ctype =
"red" if s.get(
"flags.negative")
else "yellow" 778 if (s.get(
"flags.pixel.interpolated.center")
or s.get(
"flags.pixel.saturated.center")
or 779 s.get(
"flags.pixel.cr.center")):
781 elif (s.get(
"flags.pixel.interpolated.any")
or s.get(
"flags.pixel.saturated.any")
or 782 s.get(
"flags.pixel.cr.any")):
786 ds9.dot(ptype, x, y, size=4, frame=lsstDebug.frame, ctype=ctype)
789 if display
and showPixelResiduals
and selectSources:
790 nonKernelSources = []
791 for source
in selectSources:
792 if source
not in kernelSources:
793 nonKernelSources.append(source)
795 diUtils.plotPixelResiduals(exposure,
796 subtractRes.warpedExposure,
797 subtractRes.subtractedExposure,
798 subtractRes.kernelCellSet,
799 subtractRes.psfMatchingKernel,
800 subtractRes.backgroundModel,
802 self.subtract.config.kernel.active.detectionConfig,
804 diUtils.plotPixelResiduals(exposure,
805 subtractRes.warpedExposure,
806 subtractRes.subtractedExposure,
807 subtractRes.kernelCellSet,
808 subtractRes.psfMatchingKernel,
809 subtractRes.backgroundModel,
811 self.subtract.config.kernel.active.detectionConfig,
813 if display
and showDiaSources:
814 flagChecker = SourceFlagChecker(diaSources)
815 isFlagged = [flagChecker(x)
for x
in diaSources]
816 isDipole = [x.get(
"classification.dipole")
for x
in diaSources]
817 diUtils.showDiaSources(diaSources, subtractRes.subtractedExposure, isFlagged, isDipole,
818 frame=lsstDebug.frame)
821 if display
and showDipoles:
822 DipoleAnalysis().displayDipoles(subtractRes.subtractedExposure, diaSources,
823 frame=lsstDebug.frame)
826 def _getConfigName(self):
827 """Return the name of the config dataset 829 return "%sDiff_config" % (self.config.coaddName,)
831 def _getMetadataName(self):
832 """Return the name of the metadata dataset 834 return "%sDiff_metadata" % (self.config.coaddName,)
837 """Return a dict of empty catalogs for each catalog dataset produced by this task.""" 840 return {self.config.coaddName +
"Diff_diaSrc": diaSrc}
843 def _makeArgumentParser(cls):
844 """Create an argument parser 847 parser.add_id_argument(
"--id",
"calexp", help=
"data ID, e.g. --id visit=12345 ccd=1,2")
848 parser.add_id_argument(
"--templateId",
"calexp", doMakeDataRefList=
True,
849 help=
"Optional template data ID (visit only), e.g. --templateId visit=6789")
854 winter2013WcsShift = pexConfig.Field(dtype=float, default=0.0,
855 doc=
"Shift stars going into RegisterTask by this amount")
856 winter2013WcsRms = pexConfig.Field(dtype=float, default=0.0,
857 doc=
"Perturb stars going into RegisterTask by this amount")
860 ImageDifferenceConfig.setDefaults(self)
865 """!Image difference Task used in the Winter 2013 data challege. 866 Enables testing the effects of registration shifts and scatter. 868 For use with winter 2013 simulated images: 869 Use --templateId visit=88868666 for sparse data 870 --templateId visit=22222200 for dense data (g) 871 --templateId visit=11111100 for dense data (i) 873 ConfigClass = Winter2013ImageDifferenceConfig
874 _DefaultName =
"winter2013ImageDifference" 877 ImageDifferenceTask.__init__(self, **kwargs)
880 """Fit the relative astrometry between templateSources and selectSources""" 881 if self.config.winter2013WcsShift > 0.0:
883 self.config.winter2013WcsShift)
884 cKey = templateSources[0].getTable().getCentroidKey()
885 for source
in templateSources:
886 centroid = source.get(cKey)
887 source.set(cKey, centroid+offset)
888 elif self.config.winter2013WcsRms > 0.0:
889 cKey = templateSources[0].getTable().getCentroidKey()
890 for source
in templateSources:
891 offset =
afwGeom.Extent2D(self.config.winter2013WcsRms*numpy.random.normal(),
892 self.config.winter2013WcsRms*numpy.random.normal())
893 centroid = source.get(cKey)
894 source.set(cKey, centroid+offset)
896 results = self.register.
run(templateSources, templateExposure.getWcs(),
897 templateExposure.getBBox(), selectSources)
def __init__(self, butler=None, kwargs)
Construct an ImageDifference Task.
def fitAstrometry(self, templateSources, templateExposure, selectSources)
Class for storing ordered metadata with comments.
def makeKernelBasisList(config, targetFwhmPix=None, referenceFwhmPix=None, basisDegGauss=None, metadata=None)
A mapping between the keys of two Schemas, used to copy data between them.
Parameters to control convolution.
def runDataRef(self, sensorRef, templateIdList=None)
def __init__(self, kwargs)
def getSchemaCatalogs(self)
Fit spatial kernel using approximate fluxes for candidates, and solving a linear system of equations...
template SourceMatchVector matchRaDec(SourceCatalog const &, lsst::geom::Angle, MatchControl const &)
Pass parameters to algorithms that match list of sources.
Represent a PSF as a circularly symmetrical Gaussian.
def runDebug(self, exposure, subtractRes, selectSources, kernelSources, diaSources)
Image difference Task used in the Winter 2013 data challege.
void convolve(OutImageT &convolvedImage, InImageT const &inImage, KernelT const &kernel, bool doNormalize, bool doCopyEdge=false)
Old, deprecated version of convolve.
SourceMatchVector matchXy(SourceCatalog const &cat, double radius, bool symmetric)
Compute all tuples (s1,s2,d) where s1 != s2, s1 and s2 both belong to cat, and d, the distance betwee...
def fitAstrometry(self, templateSources, templateExposure, selectSources)
def getTargetList(parsedCmd, kwargs)