36 from lsst.meas.algorithms import SourceDetectionTask, SingleGaussianPsf, ObjectSizeStarSelectorTask
37 from lsst.ip.diffim import (DipoleAnalysis, SourceFlagChecker, KernelCandidateF, makeKernelBasisList,
38 KernelCandidateQa, DiaCatalogSourceSelectorTask, DiaCatalogSourceSelectorConfig,
39 GetCoaddAsTemplateTask, GetCalexpAsTemplateTask, DipoleFitTask,
40 DecorrelateALKernelSpatialTask, subtractAlgorithmRegistry)
45 FwhmPerSigma = 2*math.sqrt(2*math.log(2))
50 """Config for ImageDifferenceTask 52 doAddCalexpBackground = pexConfig.Field(dtype=bool, default=
True,
53 doc=
"Add background to calexp before processing it. " 54 "Useful as ipDiffim does background matching.")
55 doUseRegister = pexConfig.Field(dtype=bool, default=
True,
56 doc=
"Use image-to-image registration to align template with " 58 doDebugRegister = pexConfig.Field(dtype=bool, default=
False,
59 doc=
"Writing debugging data for doUseRegister")
60 doSelectSources = pexConfig.Field(dtype=bool, default=
True,
61 doc=
"Select stars to use for kernel fitting")
62 doSelectDcrCatalog = pexConfig.Field(dtype=bool, default=
False,
63 doc=
"Select stars of extreme color as part of the control sample")
64 doSelectVariableCatalog = pexConfig.Field(dtype=bool, default=
False,
65 doc=
"Select stars that are variable to be part " 66 "of the control sample")
67 doSubtract = pexConfig.Field(dtype=bool, default=
True, doc=
"Compute subtracted exposure?")
68 doPreConvolve = pexConfig.Field(dtype=bool, default=
True,
69 doc=
"Convolve science image by its PSF before PSF-matching?")
70 useGaussianForPreConvolution = pexConfig.Field(dtype=bool, default=
True,
71 doc=
"Use a simple gaussian PSF model for pre-convolution " 72 "(else use fit PSF)? Ignored if doPreConvolve false.")
73 doDetection = pexConfig.Field(dtype=bool, default=
True, doc=
"Detect sources?")
74 doDecorrelation = pexConfig.Field(dtype=bool, default=
False,
75 doc=
"Perform diffim decorrelation to undo pixel correlation due to A&L " 76 "kernel convolution? If True, also update the diffim PSF.")
77 doMerge = pexConfig.Field(dtype=bool, default=
True,
78 doc=
"Merge positive and negative diaSources with grow radius " 79 "set by growFootprint")
80 doMatchSources = pexConfig.Field(dtype=bool, default=
True,
81 doc=
"Match diaSources with input calexp sources and ref catalog sources")
82 doMeasurement = pexConfig.Field(dtype=bool, default=
True, doc=
"Measure diaSources?")
83 doDipoleFitting = pexConfig.Field(dtype=bool, default=
True, doc=
"Measure dipoles using new algorithm?")
84 doForcedMeasurement = pexConfig.Field(dtype=bool, default=
True,
85 doc=
"Force photometer diaSource locations on PVI?")
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, goodSeeing, or dcr",
98 convolveTemplate = pexConfig.Field(
99 doc=
"Which image gets convolved (default = template)",
103 refObjLoader = pexConfig.ConfigurableField(
104 target=LoadIndexedReferenceObjectsTask,
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 forcedMeasurement = pexConfig.ConfigurableField(
137 target=ForcedMeasurementTask,
138 doc=
"Subtask to force photometer PVI at diaSource location.",
140 getTemplate = pexConfig.ConfigurableField(
141 target=GetCoaddAsTemplateTask,
142 doc=
"Subtask to retrieve template exposure and sources",
144 controlStepSize = pexConfig.Field(
145 doc=
"What step size (every Nth one) to select a control sample from the kernelSources",
149 controlRandomSeed = pexConfig.Field(
150 doc=
"Random seed for shuffing the control sample",
154 register = pexConfig.ConfigurableField(
156 doc=
"Task to enable image-to-image image registration (warping)",
158 kernelSourcesFromRef = pexConfig.Field(
159 doc=
"Select sources to measure kernel from reference catalog if True, template if false",
163 templateSipOrder = pexConfig.Field(dtype=int, default=2,
164 doc=
"Sip Order for fitting the Template Wcs " 165 "(default is too high, overfitting)")
167 growFootprint = pexConfig.Field(dtype=int, default=2,
168 doc=
"Grow positive and negative footprints by this amount before merging")
170 diaSourceMatchRadius = pexConfig.Field(dtype=float, default=0.5,
171 doc=
"Match radius (in arcseconds) " 172 "for DiaSource to Source association")
177 self.
subtract[
'al'].kernel.name =
"AL" 178 self.
subtract[
'al'].kernel.active.fitForBackground =
True 179 self.
subtract[
'al'].kernel.active.spatialKernelOrder = 1
180 self.
subtract[
'al'].kernel.active.spatialBgOrder = 0
187 self.
detection.thresholdPolarity =
"both" 189 self.
detection.reEstimateBackground =
False 190 self.
detection.thresholdType =
"pixel_stdev" 196 self.
measurement.algorithms.names.add(
'base_PeakLikelihoodFlux')
200 "id":
"objectId",
"parent":
"parentObjectId",
"coord_ra":
"coord_ra",
"coord_dec":
"coord_dec"}
208 pexConfig.Config.validate(self)
210 raise ValueError(
"Cannot run source measurement without source detection.")
212 raise ValueError(
"Cannot run source merging without source detection.")
214 raise ValueError(
"doUseRegister=True and doSelectSources=False. " 215 "Cannot run RegisterTask without selecting sources.")
218 raise ValueError(
"Mis-matched coaddName and getTemplate.coaddName in the config.")
225 return pipeBase.TaskRunner.getTargetList(parsedCmd, templateIdList=parsedCmd.templateId.idList,
230 """Subtract an image from a template and measure the result 232 ConfigClass = ImageDifferenceConfig
233 RunnerClass = ImageDifferenceTaskRunner
234 _DefaultName =
"imageDifference" 237 """!Construct an ImageDifference Task 239 @param[in] butler Butler object to use in constructing reference object loaders 241 pipeBase.CmdLineTask.__init__(self, **kwargs)
242 self.makeSubtask(
"getTemplate")
244 self.makeSubtask(
"subtract")
246 if self.config.subtract.name ==
'al' and self.config.doDecorrelation:
247 self.makeSubtask(
"decorrelate")
249 if self.config.doUseRegister:
250 self.makeSubtask(
"register")
251 self.
schema = afwTable.SourceTable.makeMinimalSchema()
253 if self.config.doSelectSources:
254 self.makeSubtask(
"sourceSelector")
255 if self.config.kernelSourcesFromRef:
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 765 disp = afwDisplay.getDisplay(frame=lsstDebug.frame)
766 if not maskTransparency:
768 disp.setMaskTransparency(maskTransparency)
770 if display
and showSubtracted:
771 disp.mtv(subtractRes.subtractedExposure, title=
"Subtracted image")
772 mi = subtractRes.subtractedExposure.getMaskedImage()
773 x0, y0 = mi.getX0(), mi.getY0()
774 with disp.Buffering():
776 x, y = s.getX() - x0, s.getY() - y0
777 ctype =
"red" if s.get(
"flags_negative")
else "yellow" 778 if (s.get(
"base_PixelFlags_flag_interpolatedCenter")
or 779 s.get(
"base_PixelFlags_flag_saturatedCenter")
or 780 s.get(
"base_PixelFlags_flag_crCenter")):
782 elif (s.get(
"base_PixelFlags_flag_interpolated")
or 783 s.get(
"base_PixelFlags_flag_saturated")
or 784 s.get(
"base_PixelFlags_flag_cr")):
788 disp.dot(ptype, x, y, size=4, ctype=ctype)
791 if display
and showPixelResiduals
and selectSources:
792 nonKernelSources = []
793 for source
in selectSources:
794 if source
not in kernelSources:
795 nonKernelSources.append(source)
797 diUtils.plotPixelResiduals(exposure,
798 subtractRes.warpedExposure,
799 subtractRes.subtractedExposure,
800 subtractRes.kernelCellSet,
801 subtractRes.psfMatchingKernel,
802 subtractRes.backgroundModel,
804 self.subtract.config.kernel.active.detectionConfig,
806 diUtils.plotPixelResiduals(exposure,
807 subtractRes.warpedExposure,
808 subtractRes.subtractedExposure,
809 subtractRes.kernelCellSet,
810 subtractRes.psfMatchingKernel,
811 subtractRes.backgroundModel,
813 self.subtract.config.kernel.active.detectionConfig,
815 if display
and showDiaSources:
816 flagChecker = SourceFlagChecker(diaSources)
817 isFlagged = [flagChecker(x)
for x
in diaSources]
818 isDipole = [x.get(
"ip_diffim_ClassificationDipole_value")
for x
in diaSources]
819 diUtils.showDiaSources(diaSources, subtractRes.subtractedExposure, isFlagged, isDipole,
820 frame=lsstDebug.frame)
823 if display
and showDipoles:
824 DipoleAnalysis().displayDipoles(subtractRes.subtractedExposure, diaSources,
825 frame=lsstDebug.frame)
828 def _getConfigName(self):
829 """Return the name of the config dataset 831 return "%sDiff_config" % (self.config.coaddName,)
833 def _getMetadataName(self):
834 """Return the name of the metadata dataset 836 return "%sDiff_metadata" % (self.config.coaddName,)
839 """Return a dict of empty catalogs for each catalog dataset produced by this task.""" 842 return {self.config.coaddName +
"Diff_diaSrc": diaSrc}
845 def _makeArgumentParser(cls):
846 """Create an argument parser 849 parser.add_id_argument(
"--id",
"calexp", help=
"data ID, e.g. --id visit=12345 ccd=1,2")
850 parser.add_id_argument(
"--templateId",
"calexp", doMakeDataRefList=
True,
851 help=
"Template data ID in case of calexp template," 852 " e.g. --templateId visit=6789")
857 winter2013WcsShift = pexConfig.Field(dtype=float, default=0.0,
858 doc=
"Shift stars going into RegisterTask by this amount")
859 winter2013WcsRms = pexConfig.Field(dtype=float, default=0.0,
860 doc=
"Perturb stars going into RegisterTask by this amount")
863 ImageDifferenceConfig.setDefaults(self)
868 """!Image difference Task used in the Winter 2013 data challege. 869 Enables testing the effects of registration shifts and scatter. 871 For use with winter 2013 simulated images: 872 Use --templateId visit=88868666 for sparse data 873 --templateId visit=22222200 for dense data (g) 874 --templateId visit=11111100 for dense data (i) 876 ConfigClass = Winter2013ImageDifferenceConfig
877 _DefaultName =
"winter2013ImageDifference" 880 ImageDifferenceTask.__init__(self, **kwargs)
883 """Fit the relative astrometry between templateSources and selectSources""" 884 if self.config.winter2013WcsShift > 0.0:
886 self.config.winter2013WcsShift)
887 cKey = templateSources[0].getTable().getCentroidKey()
888 for source
in templateSources:
889 centroid = source.get(cKey)
890 source.set(cKey, centroid + offset)
891 elif self.config.winter2013WcsRms > 0.0:
892 cKey = templateSources[0].getTable().getCentroidKey()
893 for source
in templateSources:
894 offset =
afwGeom.Extent2D(self.config.winter2013WcsRms*numpy.random.normal(),
895 self.config.winter2013WcsRms*numpy.random.normal())
896 centroid = source.get(cKey)
897 source.set(cKey, centroid + offset)
899 results = self.register.
run(templateSources, templateExposure.getWcs(),
900 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)