26 import lsst.pex.config
as pexConfig
33 from lsst.meas.base import ForcedMeasurementTask, EvaluateLocalCalibrationTask
37 from lsst.meas.algorithms import SourceDetectionTask, SingleGaussianPsf, ObjectSizeStarSelectorTask
38 from lsst.ip.diffim import (DipoleAnalysis, SourceFlagChecker, KernelCandidateF, makeKernelBasisList,
39 KernelCandidateQa, DiaCatalogSourceSelectorTask, DiaCatalogSourceSelectorConfig,
40 GetCoaddAsTemplateTask, GetCalexpAsTemplateTask, DipoleFitTask,
41 DecorrelateALKernelSpatialTask, subtractAlgorithmRegistry)
46 __all__ = [
"ImageDifferenceConfig",
"ImageDifferenceTask"]
47 FwhmPerSigma = 2*math.sqrt(2*math.log(2))
52 """Config for ImageDifferenceTask 54 doAddCalexpBackground = pexConfig.Field(dtype=bool, default=
False,
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 doScaleTemplateVariance = pexConfig.Field(dtype=bool, default=
False,
73 doc=
"Scale variance of the template before PSF matching")
74 useGaussianForPreConvolution = pexConfig.Field(dtype=bool, default=
True,
75 doc=
"Use a simple gaussian PSF model for pre-convolution " 76 "(else use fit PSF)? Ignored if doPreConvolve false.")
77 doDetection = pexConfig.Field(dtype=bool, default=
True, doc=
"Detect sources?")
78 doDecorrelation = pexConfig.Field(dtype=bool, default=
False,
79 doc=
"Perform diffim decorrelation to undo pixel correlation due to A&L " 80 "kernel convolution? If True, also update the diffim PSF.")
81 doMerge = pexConfig.Field(dtype=bool, default=
True,
82 doc=
"Merge positive and negative diaSources with grow radius " 83 "set by growFootprint")
84 doMatchSources = pexConfig.Field(dtype=bool, default=
True,
85 doc=
"Match diaSources with input calexp sources and ref catalog sources")
86 doMeasurement = pexConfig.Field(dtype=bool, default=
True, doc=
"Measure diaSources?")
87 doEvalLocCalibration = pexConfig.Field(
90 doc=
"Store calibration products (local wcs and photoCalib) in the " 91 "output DiaSource catalog.")
92 doDipoleFitting = pexConfig.Field(dtype=bool, default=
True, doc=
"Measure dipoles using new algorithm?")
93 doForcedMeasurement = pexConfig.Field(
96 doc=
"Force photometer diaSource locations on PVI?")
97 doWriteSubtractedExp = pexConfig.Field(dtype=bool, default=
True, doc=
"Write difference exposure?")
98 doWriteMatchedExp = pexConfig.Field(dtype=bool, default=
False,
99 doc=
"Write warped and PSF-matched template coadd exposure?")
100 doWriteSources = pexConfig.Field(dtype=bool, default=
True, doc=
"Write sources?")
101 doAddMetrics = pexConfig.Field(dtype=bool, default=
True,
102 doc=
"Add columns to the source table to hold analysis metrics?")
104 coaddName = pexConfig.Field(
105 doc=
"coadd name: typically one of deep, goodSeeing, or dcr",
109 convolveTemplate = pexConfig.Field(
110 doc=
"Which image gets convolved (default = template)",
114 refObjLoader = pexConfig.ConfigurableField(
115 target=LoadIndexedReferenceObjectsTask,
116 doc=
"reference object loader",
118 astrometer = pexConfig.ConfigurableField(
119 target=AstrometryTask,
120 doc=
"astrometry task; used to match sources to reference objects, but not to fit a WCS",
122 sourceSelector = pexConfig.ConfigurableField(
123 target=ObjectSizeStarSelectorTask,
124 doc=
"Source selection algorithm",
126 subtract = subtractAlgorithmRegistry.makeField(
"Subtraction Algorithm", default=
"al")
127 decorrelate = pexConfig.ConfigurableField(
128 target=DecorrelateALKernelSpatialTask,
129 doc=
"Decorrelate effects of A&L kernel convolution on image difference, only if doSubtract is True. " 130 "If this option is enabled, then detection.thresholdValue should be set to 5.0 (rather than the " 133 doSpatiallyVarying = pexConfig.Field(
136 doc=
"If using Zogy or A&L decorrelation, perform these on a grid across the " 137 "image in order to allow for spatial variations" 139 detection = pexConfig.ConfigurableField(
140 target=SourceDetectionTask,
141 doc=
"Low-threshold detection for final measurement",
143 measurement = pexConfig.ConfigurableField(
144 target=DipoleFitTask,
145 doc=
"Enable updated dipole fitting method",
147 evalLocCalib = pexConfig.ConfigurableField(
148 target=EvaluateLocalCalibrationTask,
149 doc=
"Task to strip calibrations from an exposure and store their " 150 "local values in the output DiaSource catalog." 152 forcedMeasurement = pexConfig.ConfigurableField(
153 target=ForcedMeasurementTask,
154 doc=
"Subtask to force photometer PVI at diaSource location.",
156 getTemplate = pexConfig.ConfigurableField(
157 target=GetCoaddAsTemplateTask,
158 doc=
"Subtask to retrieve template exposure and sources",
160 scaleVariance = pexConfig.ConfigurableField(
161 target=ScaleVarianceTask,
162 doc=
"Subtask to rescale the variance of the template " 163 "to the statistically expected level" 165 controlStepSize = pexConfig.Field(
166 doc=
"What step size (every Nth one) to select a control sample from the kernelSources",
170 controlRandomSeed = pexConfig.Field(
171 doc=
"Random seed for shuffing the control sample",
175 register = pexConfig.ConfigurableField(
177 doc=
"Task to enable image-to-image image registration (warping)",
179 kernelSourcesFromRef = pexConfig.Field(
180 doc=
"Select sources to measure kernel from reference catalog if True, template if false",
184 templateSipOrder = pexConfig.Field(
185 dtype=int, default=2,
186 doc=
"Sip Order for fitting the Template Wcs (default is too high, overfitting)" 188 growFootprint = pexConfig.Field(
189 dtype=int, default=2,
190 doc=
"Grow positive and negative footprints by this amount before merging" 192 diaSourceMatchRadius = pexConfig.Field(
193 dtype=float, default=0.5,
194 doc=
"Match radius (in arcseconds) for DiaSource to Source association" 200 self.
subtract[
'al'].kernel.name =
"AL" 201 self.
subtract[
'al'].kernel.active.fitForBackground =
True 202 self.
subtract[
'al'].kernel.active.spatialKernelOrder = 1
203 self.
subtract[
'al'].kernel.active.spatialBgOrder = 2
210 self.
detection.thresholdPolarity =
"both" 212 self.
detection.reEstimateBackground =
False 213 self.
detection.thresholdType =
"pixel_stdev" 219 self.
measurement.algorithms.names.add(
'base_PeakLikelihoodFlux')
223 "id":
"objectId",
"parent":
"parentObjectId",
"coord_ra":
"coord_ra",
"coord_dec":
"coord_dec"}
231 pexConfig.Config.validate(self)
233 raise ValueError(
"Subtraction must be enabled for kernel metrics calculation.")
235 raise ValueError(
"Either doSubtract or doDetection must be enabled.")
237 raise ValueError(
"Kernel metrics does not exist in zogy subtraction.")
239 raise ValueError(
"Cannot run source measurement without source detection.")
241 raise ValueError(
"Cannot run source merging without source detection.")
243 raise ValueError(
"doUseRegister=True and doSelectSources=False. " 244 "Cannot run RegisterTask without selecting sources.")
246 raise ValueError(
"doPreConvolve=True and doDecorrelation=True and " 247 "convolveTemplate=False is not supported.")
250 raise ValueError(
"Mis-matched coaddName and getTemplate.coaddName in the config.")
257 return pipeBase.TaskRunner.getTargetList(parsedCmd, templateIdList=parsedCmd.templateId.idList,
262 """Subtract an image from a template and measure the result 264 ConfigClass = ImageDifferenceConfig
265 RunnerClass = ImageDifferenceTaskRunner
266 _DefaultName =
"imageDifference" 269 """!Construct an ImageDifference Task 271 @param[in] butler Butler object to use in constructing reference object loaders 273 pipeBase.CmdLineTask.__init__(self, **kwargs)
274 self.makeSubtask(
"getTemplate")
276 self.makeSubtask(
"subtract")
278 if self.config.subtract.name ==
'al' and self.config.doDecorrelation:
279 self.makeSubtask(
"decorrelate")
281 if self.config.doScaleTemplateVariance:
282 self.makeSubtask(
"scaleVariance")
284 if self.config.doUseRegister:
285 self.makeSubtask(
"register")
286 self.
schema = afwTable.SourceTable.makeMinimalSchema()
288 if self.config.doSelectSources:
289 self.makeSubtask(
"sourceSelector")
290 if self.config.kernelSourcesFromRef:
291 self.makeSubtask(
'refObjLoader', butler=butler)
292 self.makeSubtask(
"astrometer", refObjLoader=self.refObjLoader)
295 if self.config.doDetection:
296 self.makeSubtask(
"detection", schema=self.
schema)
297 if self.config.doMeasurement:
298 self.makeSubtask(
"measurement", schema=self.
schema,
300 if self.config.doEvalLocCalibration
and self.config.doMeasurement:
301 self.makeSubtask(
"evalLocCalib", schema=self.
schema)
302 if self.config.doForcedMeasurement:
304 "ip_diffim_forced_PsfFlux_instFlux",
"D",
305 "Forced PSF flux measured on the direct image.")
307 "ip_diffim_forced_PsfFlux_instFluxErr",
"D",
308 "Forced PSF flux error measured on the direct image.")
310 "ip_diffim_forced_PsfFlux_area",
"F",
311 "Forced PSF flux effective area of PSF.",
314 "ip_diffim_forced_PsfFlux_flag",
"Flag",
315 "Forced PSF flux general failure flag.")
317 "ip_diffim_forced_PsfFlux_flag_noGoodPixels",
"Flag",
318 "Forced PSF flux not enough non-rejected pixels in data to attempt the fit.")
320 "ip_diffim_forced_PsfFlux_flag_edge",
"Flag",
321 "Forced PSF flux object was too close to the edge of the image to use the full PSF model.")
322 self.makeSubtask(
"forcedMeasurement", refSchema=self.
schema)
323 if self.config.doMatchSources:
324 self.
schema.addField(
"refMatchId",
"L",
"unique id of reference catalog match")
325 self.
schema.addField(
"srcMatchId",
"L",
"unique id of source match")
329 """Create IdFactory instance for unique 64 bit diaSource id-s. 337 Number of used bits in ``expId``. 341 The diasource id-s consists of the ``expId`` stored fixed in the highest value 342 ``expBits`` of the 64-bit integer plus (bitwise or) a generated sequence number in the 343 low value end of the integer. 347 idFactory: `lsst.afw.table.IdFactory` 349 return afwTable.IdFactory.makeSource(expId, 64 - expBits)
353 """Subtract an image from a template coadd and measure the result. 355 Data I/O wrapper around `run` using the butler in Gen2. 359 sensorRef : `lsst.daf.persistence.ButlerDataRef` 360 Sensor-level butler data reference, used for the following data products: 367 - self.config.coaddName + "Coadd_skyMap" 368 - self.config.coaddName + "Coadd" 369 Input or output, depending on config: 370 - self.config.coaddName + "Diff_subtractedExp" 371 Output, depending on config: 372 - self.config.coaddName + "Diff_matchedExp" 373 - self.config.coaddName + "Diff_src" 377 results : `lsst.pipe.base.Struct` 378 Returns the Struct by `run`. 380 subtractedExposureName = self.config.coaddName +
"Diff_differenceExp" 381 subtractedExposure =
None 383 calexpBackgroundExposure =
None 384 self.log.
info(
"Processing %s" % (sensorRef.dataId))
389 idFactory = self.
makeIdFactory(expId=int(sensorRef.get(
"ccdExposureId")),
390 expBits=sensorRef.get(
"ccdExposureId_bits"))
391 if self.config.doAddCalexpBackground:
392 calexpBackgroundExposure = sensorRef.get(
"calexpBackground")
395 exposure = sensorRef.get(
"calexp", immediate=
True)
398 template = self.getTemplate.
run(exposure, sensorRef, templateIdList=templateIdList)
400 if sensorRef.datasetExists(
"src"):
401 self.log.
info(
"Source selection via src product")
403 selectSources = sensorRef.get(
"src")
405 if not self.config.doSubtract
and self.config.doDetection:
407 subtractedExposure = sensorRef.get(subtractedExposureName)
410 results = self.
run(exposure=exposure,
411 selectSources=selectSources,
412 templateExposure=template.exposure,
413 templateSources=template.sources,
415 calexpBackgroundExposure=calexpBackgroundExposure,
416 subtractedExposure=subtractedExposure)
418 if self.config.doWriteSources
and results.diaSources
is not None:
419 sensorRef.put(results.diaSources, self.config.coaddName +
"Diff_diaSrc")
420 if self.config.doWriteMatchedExp:
421 sensorRef.put(results.matchedExposure, self.config.coaddName +
"Diff_matchedExp")
422 if self.config.doAddMetrics
and self.config.doSelectSources:
423 sensorRef.put(results.selectSources, self.config.coaddName +
"Diff_kernelSrc")
424 if self.config.doWriteSubtractedExp:
425 sensorRef.put(results.subtractedExposure, subtractedExposureName)
428 def run(self, exposure=None, selectSources=None, templateExposure=None, templateSources=None,
429 idFactory=None, calexpBackgroundExposure=None, subtractedExposure=None):
430 """PSF matches, subtract two images and perform detection on the difference image. 434 exposure : `lsst.afw.image.ExposureF`, optional 435 The science exposure, the minuend in the image subtraction. 436 Can be None only if ``config.doSubtract==False``. 437 selectSources : `lsst.afw.table.SourceCatalog`, optional 438 Identified sources on the science exposure. This catalog is used to 439 select sources in order to perform the AL PSF matching on stamp images 440 around them. The selection steps depend on config options and whether 441 ``templateSources`` and ``matchingSources`` specified. 442 templateExposure : `lsst.afw.image.ExposureF`, optional 443 The template to be subtracted from ``exposure`` in the image subtraction. 444 The template exposure should cover the same sky area as the science exposure. 445 It is either a stich of patches of a coadd skymap image or a calexp 446 of the same pointing as the science exposure. Can be None only 447 if ``config.doSubtract==False`` and ``subtractedExposure`` is not None. 448 templateSources : `lsst.afw.table.SourceCatalog`, optional 449 Identified sources on the template exposure. 450 idFactory : `lsst.afw.table.IdFactory` 451 Generator object to assign ids to detected sources in the difference image. 452 calexpBackgroundExposure : `lsst.afw.image.ExposureF`, optional 453 Background exposure to be added back to the science exposure 454 if ``config.doAddCalexpBackground==True`` 455 subtractedExposure : `lsst.afw.image.ExposureF`, optional 456 If ``config.doSubtract==False`` and ``config.doDetection==True``, 457 performs the post subtraction source detection only on this exposure. 458 Otherwise should be None. 462 results : `lsst.pipe.base.Struct` 463 ``subtractedExposure`` : `lsst.afw.image.ExposureF` 465 ``matchedExposure`` : `lsst.afw.image.ExposureF` 466 The matched PSF exposure. 467 ``subtractRes`` : `lsst.pipe.base.Struct` 468 The returned result structure of the ImagePsfMatchTask subtask. 469 ``diaSources`` : `lsst.afw.table.SourceCatalog` 470 The catalog of detected sources. 471 ``selectSources`` : `lsst.afw.table.SourceCatalog` 472 The input source catalog with optionally added Qa information. 476 The following major steps are included: 478 - warp template coadd to match WCS of image 479 - PSF match image to warped template 480 - subtract image from PSF-matched, warped template 484 For details about the image subtraction configuration modes 485 see `lsst.ip.diffim`. 488 controlSources =
None 492 if self.config.doAddCalexpBackground:
493 mi = exposure.getMaskedImage()
494 mi += calexpBackgroundExposure.getImage()
496 if not exposure.hasPsf():
497 raise pipeBase.TaskError(
"Exposure has no psf")
498 sciencePsf = exposure.getPsf()
500 if self.config.doSubtract:
501 if self.config.doScaleTemplateVariance:
502 templateVarFactor = self.scaleVariance.
run(
503 templateExposure.getMaskedImage())
504 self.metadata.add(
"scaleTemplateVarianceFactor", templateVarFactor)
506 if self.config.subtract.name ==
'zogy':
507 subtractRes = self.subtract.subtractExposures(templateExposure, exposure,
509 spatiallyVarying=self.config.doSpatiallyVarying,
510 doPreConvolve=self.config.doPreConvolve)
511 subtractedExposure = subtractRes.subtractedExposure
513 elif self.config.subtract.name ==
'al':
515 scienceSigmaOrig = sciencePsf.computeShape().getDeterminantRadius()
516 templateSigma = templateExposure.getPsf().computeShape().getDeterminantRadius()
524 if self.config.doPreConvolve:
527 srcMI = exposure.getMaskedImage()
528 destMI = srcMI.Factory(srcMI.getDimensions())
530 if self.config.useGaussianForPreConvolution:
532 kWidth, kHeight = sciencePsf.getLocalKernel().getDimensions()
538 exposure.setMaskedImage(destMI)
539 scienceSigmaPost = scienceSigmaOrig*math.sqrt(2)
541 scienceSigmaPost = scienceSigmaOrig
546 if self.config.doSelectSources:
547 if selectSources
is None:
548 self.log.
warn(
"Src product does not exist; running detection, measurement, selection")
550 selectSources = self.subtract.getSelectSources(
552 sigma=scienceSigmaPost,
553 doSmooth=
not self.doPreConvolve,
557 if self.config.doAddMetrics:
561 referenceFwhmPix=scienceSigmaPost*FwhmPerSigma,
562 targetFwhmPix=templateSigma*FwhmPerSigma))
569 kcQa = KernelCandidateQa(nparam)
570 selectSources = kcQa.addToSchema(selectSources)
571 if self.config.kernelSourcesFromRef:
573 astromRet = self.astrometer.loadAndMatch(exposure=exposure, sourceCat=selectSources)
574 matches = astromRet.matches
575 elif templateSources:
578 mc.findOnlyClosest =
False 582 raise RuntimeError(
"doSelectSources=True and kernelSourcesFromRef=False," 583 "but template sources not available. Cannot match science " 584 "sources with template sources. Run process* on data from " 585 "which templates are built.")
587 kernelSources = self.sourceSelector.
run(selectSources, exposure=exposure,
588 matches=matches).sourceCat
589 random.shuffle(kernelSources, random.random)
590 controlSources = kernelSources[::self.config.controlStepSize]
591 kernelSources = [k
for i, k
in enumerate(kernelSources)
592 if i % self.config.controlStepSize]
594 if self.config.doSelectDcrCatalog:
595 redSelector = DiaCatalogSourceSelectorTask(
596 DiaCatalogSourceSelectorConfig(grMin=self.sourceSelector.config.grMax,
598 redSources = redSelector.selectStars(exposure, selectSources, matches=matches).starCat
599 controlSources.extend(redSources)
601 blueSelector = DiaCatalogSourceSelectorTask(
602 DiaCatalogSourceSelectorConfig(grMin=-99.999,
603 grMax=self.sourceSelector.config.grMin))
604 blueSources = blueSelector.selectStars(exposure, selectSources,
605 matches=matches).starCat
606 controlSources.extend(blueSources)
608 if self.config.doSelectVariableCatalog:
609 varSelector = DiaCatalogSourceSelectorTask(
610 DiaCatalogSourceSelectorConfig(includeVariable=
True))
611 varSources = varSelector.selectStars(exposure, selectSources, matches=matches).starCat
612 controlSources.extend(varSources)
614 self.log.
info(
"Selected %d / %d sources for Psf matching (%d for control sample)" 615 % (len(kernelSources), len(selectSources), len(controlSources)))
619 if self.config.doUseRegister:
620 self.log.
info(
"Registering images")
622 if templateSources
is None:
626 templateSigma = templateExposure.getPsf().computeShape().getDeterminantRadius()
627 templateSources = self.subtract.getSelectSources(
636 wcsResults = self.
fitAstrometry(templateSources, templateExposure, selectSources)
637 warpedExp = self.register.
warpExposure(templateExposure, wcsResults.wcs,
638 exposure.getWcs(), exposure.getBBox())
639 templateExposure = warpedExp
644 if self.config.doDebugRegister:
646 srcToMatch = {x.second.getId(): x.first
for x
in matches}
648 refCoordKey = wcsResults.matches[0].first.getTable().getCoordKey()
649 inCentroidKey = wcsResults.matches[0].second.getTable().getCentroidKey()
650 sids = [m.first.getId()
for m
in wcsResults.matches]
651 positions = [m.first.get(refCoordKey)
for m
in wcsResults.matches]
652 residuals = [m.first.get(refCoordKey).getOffsetFrom(wcsResults.wcs.pixelToSky(
653 m.second.get(inCentroidKey)))
for m
in wcsResults.matches]
654 allresids = dict(zip(sids, zip(positions, residuals)))
656 cresiduals = [m.first.get(refCoordKey).getTangentPlaneOffset(
657 wcsResults.wcs.pixelToSky(
658 m.second.get(inCentroidKey)))
for m
in wcsResults.matches]
659 colors = numpy.array([-2.5*numpy.log10(srcToMatch[x].get(
"g")) +
660 2.5*numpy.log10(srcToMatch[x].get(
"r")) 661 for x
in sids
if x
in srcToMatch.keys()])
662 dlong = numpy.array([r[0].asArcseconds()
for s, r
in zip(sids, cresiduals)
663 if s
in srcToMatch.keys()])
664 dlat = numpy.array([r[1].asArcseconds()
for s, r
in zip(sids, cresiduals)
665 if s
in srcToMatch.keys()])
666 idx1 = numpy.where(colors < self.sourceSelector.config.grMin)
667 idx2 = numpy.where((colors >= self.sourceSelector.config.grMin) &
668 (colors <= self.sourceSelector.config.grMax))
669 idx3 = numpy.where(colors > self.sourceSelector.config.grMax)
670 rms1Long = IqrToSigma*(
671 (numpy.percentile(dlong[idx1], 75) - numpy.percentile(dlong[idx1], 25)))
672 rms1Lat = IqrToSigma*(numpy.percentile(dlat[idx1], 75) -
673 numpy.percentile(dlat[idx1], 25))
674 rms2Long = IqrToSigma*(
675 (numpy.percentile(dlong[idx2], 75) - numpy.percentile(dlong[idx2], 25)))
676 rms2Lat = IqrToSigma*(numpy.percentile(dlat[idx2], 75) -
677 numpy.percentile(dlat[idx2], 25))
678 rms3Long = IqrToSigma*(
679 (numpy.percentile(dlong[idx3], 75) - numpy.percentile(dlong[idx3], 25)))
680 rms3Lat = IqrToSigma*(numpy.percentile(dlat[idx3], 75) -
681 numpy.percentile(dlat[idx3], 25))
682 self.log.
info(
"Blue star offsets'': %.3f %.3f, %.3f %.3f" %
683 (numpy.median(dlong[idx1]), rms1Long,
684 numpy.median(dlat[idx1]), rms1Lat))
685 self.log.
info(
"Green star offsets'': %.3f %.3f, %.3f %.3f" %
686 (numpy.median(dlong[idx2]), rms2Long,
687 numpy.median(dlat[idx2]), rms2Lat))
688 self.log.
info(
"Red star offsets'': %.3f %.3f, %.3f %.3f" %
689 (numpy.median(dlong[idx3]), rms3Long,
690 numpy.median(dlat[idx3]), rms3Lat))
692 self.metadata.add(
"RegisterBlueLongOffsetMedian", numpy.median(dlong[idx1]))
693 self.metadata.add(
"RegisterGreenLongOffsetMedian", numpy.median(dlong[idx2]))
694 self.metadata.add(
"RegisterRedLongOffsetMedian", numpy.median(dlong[idx3]))
695 self.metadata.add(
"RegisterBlueLongOffsetStd", rms1Long)
696 self.metadata.add(
"RegisterGreenLongOffsetStd", rms2Long)
697 self.metadata.add(
"RegisterRedLongOffsetStd", rms3Long)
699 self.metadata.add(
"RegisterBlueLatOffsetMedian", numpy.median(dlat[idx1]))
700 self.metadata.add(
"RegisterGreenLatOffsetMedian", numpy.median(dlat[idx2]))
701 self.metadata.add(
"RegisterRedLatOffsetMedian", numpy.median(dlat[idx3]))
702 self.metadata.add(
"RegisterBlueLatOffsetStd", rms1Lat)
703 self.metadata.add(
"RegisterGreenLatOffsetStd", rms2Lat)
704 self.metadata.add(
"RegisterRedLatOffsetStd", rms3Lat)
711 self.log.
info(
"Subtracting images")
712 subtractRes = self.subtract.subtractExposures(
713 templateExposure=templateExposure,
714 scienceExposure=exposure,
715 candidateList=kernelSources,
716 convolveTemplate=self.config.convolveTemplate,
717 doWarping=
not self.config.doUseRegister
719 subtractedExposure = subtractRes.subtractedExposure
721 if self.config.doDetection:
722 self.log.
info(
"Computing diffim PSF")
725 if not subtractedExposure.hasPsf():
726 if self.config.convolveTemplate:
727 subtractedExposure.setPsf(exposure.getPsf())
729 subtractedExposure.setPsf(templateExposure.getPsf())
736 if self.config.doDecorrelation
and self.config.doSubtract:
738 if preConvPsf
is not None:
739 preConvKernel = preConvPsf.getLocalKernel()
740 if self.config.convolveTemplate:
741 self.log.
info(
"Decorrelation after template image convolution")
742 decorrResult = self.decorrelate.
run(exposure, templateExposure,
744 subtractRes.psfMatchingKernel,
745 spatiallyVarying=self.config.doSpatiallyVarying,
746 preConvKernel=preConvKernel)
748 self.log.
info(
"Decorrelation after science image convolution")
749 decorrResult = self.decorrelate.
run(templateExposure, exposure,
751 subtractRes.psfMatchingKernel,
752 spatiallyVarying=self.config.doSpatiallyVarying,
753 preConvKernel=preConvKernel)
754 subtractedExposure = decorrResult.correctedExposure
758 if self.config.doDetection:
759 self.log.
info(
"Running diaSource detection")
761 mask = subtractedExposure.getMaskedImage().getMask()
762 mask &= ~(mask.getPlaneBitMask(
"DETECTED") | mask.getPlaneBitMask(
"DETECTED_NEGATIVE"))
764 table = afwTable.SourceTable.make(self.
schema, idFactory)
766 results = self.detection.makeSourceCatalog(
768 exposure=subtractedExposure,
769 doSmooth=
not self.config.doPreConvolve
772 if self.config.doMerge:
773 fpSet = results.fpSets.positive
774 fpSet.merge(results.fpSets.negative, self.config.growFootprint,
775 self.config.growFootprint,
False)
777 fpSet.makeSources(diaSources)
778 self.log.
info(
"Merging detections into %d sources" % (len(diaSources)))
780 diaSources = results.sources
782 if self.config.doMeasurement:
783 newDipoleFitting = self.config.doDipoleFitting
784 self.log.
info(
"Running diaSource measurement: newDipoleFitting=%r", newDipoleFitting)
785 if not newDipoleFitting:
787 self.measurement.
run(diaSources, subtractedExposure)
790 if self.config.doSubtract
and 'matchedExposure' in subtractRes.getDict():
791 self.measurement.
run(diaSources, subtractedExposure, exposure,
792 subtractRes.matchedExposure)
794 self.measurement.
run(diaSources, subtractedExposure, exposure)
796 if self.config.doEvalLocCalibration
and self.config.doMeasurement:
797 self.evalLocCalib.
run(diaSources, subtractedExposure)
799 if self.config.doForcedMeasurement:
802 forcedSources = self.forcedMeasurement.generateMeasCat(
803 exposure, diaSources, subtractedExposure.getWcs())
804 self.forcedMeasurement.
run(forcedSources, exposure, diaSources, subtractedExposure.getWcs())
806 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_instFlux")[0],
807 "ip_diffim_forced_PsfFlux_instFlux",
True)
808 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_instFluxErr")[0],
809 "ip_diffim_forced_PsfFlux_instFluxErr",
True)
810 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_area")[0],
811 "ip_diffim_forced_PsfFlux_area",
True)
812 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_flag")[0],
813 "ip_diffim_forced_PsfFlux_flag",
True)
814 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_flag_noGoodPixels")[0],
815 "ip_diffim_forced_PsfFlux_flag_noGoodPixels",
True)
816 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_flag_edge")[0],
817 "ip_diffim_forced_PsfFlux_flag_edge",
True)
818 for diaSource, forcedSource
in zip(diaSources, forcedSources):
819 diaSource.assign(forcedSource, mapper)
822 if self.config.doMatchSources:
823 if selectSources
is not None:
825 matchRadAsec = self.config.diaSourceMatchRadius
826 matchRadPixel = matchRadAsec/exposure.getWcs().getPixelScale().asArcseconds()
829 srcMatchDict = dict([(srcMatch.second.getId(), srcMatch.first.getId())
for 830 srcMatch
in srcMatches])
831 self.log.
info(
"Matched %d / %d diaSources to sources" % (len(srcMatchDict),
834 self.log.
warn(
"Src product does not exist; cannot match with diaSources")
839 refAstromConfig.matcher.maxMatchDistArcSec = matchRadAsec
841 astromRet = refAstrometer.run(exposure=exposure, sourceCat=diaSources)
842 refMatches = astromRet.matches
843 if refMatches
is None:
844 self.log.
warn(
"No diaSource matches with reference catalog")
847 self.log.
info(
"Matched %d / %d diaSources to reference catalog" % (len(refMatches),
849 refMatchDict = dict([(refMatch.second.getId(), refMatch.first.getId())
for 850 refMatch
in refMatches])
853 for diaSource
in diaSources:
854 sid = diaSource.getId()
855 if sid
in srcMatchDict:
856 diaSource.set(
"srcMatchId", srcMatchDict[sid])
857 if sid
in refMatchDict:
858 diaSource.set(
"refMatchId", refMatchDict[sid])
860 if self.config.doAddMetrics
and self.config.doSelectSources:
861 self.log.
info(
"Evaluating metrics and control sample")
864 for cell
in subtractRes.kernelCellSet.getCellList():
865 for cand
in cell.begin(
False):
866 kernelCandList.append(cand)
869 basisList = kernelCandList[0].getKernel(KernelCandidateF.ORIG).getKernelList()
870 nparam = len(kernelCandList[0].getKernel(KernelCandidateF.ORIG).getKernelParameters())
873 diffimTools.sourceTableToCandidateList(controlSources,
874 subtractRes.warpedExposure, exposure,
875 self.config.subtract.kernel.active,
876 self.config.subtract.kernel.active.detectionConfig,
877 self.log, doBuild=
True, basisList=basisList))
879 KernelCandidateQa.apply(kernelCandList, subtractRes.psfMatchingKernel,
880 subtractRes.backgroundModel, dof=nparam)
881 KernelCandidateQa.apply(controlCandList, subtractRes.psfMatchingKernel,
882 subtractRes.backgroundModel)
884 if self.config.doDetection:
885 KernelCandidateQa.aggregate(selectSources, self.metadata, allresids, diaSources)
887 KernelCandidateQa.aggregate(selectSources, self.metadata, allresids)
889 self.
runDebug(exposure, subtractRes, selectSources, kernelSources, diaSources)
890 return pipeBase.Struct(
891 subtractedExposure=subtractedExposure,
892 matchedExposure=subtractRes.matchedExposure,
893 subtractRes=subtractRes,
894 diaSources=diaSources,
895 selectSources=selectSources
899 """Fit the relative astrometry between templateSources and selectSources 904 Remove this method. It originally fit a new WCS to the template before calling register.run 905 because our TAN-SIP fitter behaved badly for points far from CRPIX, but that's been fixed. 906 It remains because a subtask overrides it. 908 results = self.register.
run(templateSources, templateExposure.getWcs(),
909 templateExposure.getBBox(), selectSources)
912 def runDebug(self, exposure, subtractRes, selectSources, kernelSources, diaSources):
913 """Make debug plots and displays. 917 Test and update for current debug display and slot names 927 disp = afwDisplay.getDisplay(frame=lsstDebug.frame)
928 if not maskTransparency:
930 disp.setMaskTransparency(maskTransparency)
932 if display
and showSubtracted:
933 disp.mtv(subtractRes.subtractedExposure, title=
"Subtracted image")
934 mi = subtractRes.subtractedExposure.getMaskedImage()
935 x0, y0 = mi.getX0(), mi.getY0()
936 with disp.Buffering():
938 x, y = s.getX() - x0, s.getY() - y0
939 ctype =
"red" if s.get(
"flags_negative")
else "yellow" 940 if (s.get(
"base_PixelFlags_flag_interpolatedCenter")
or 941 s.get(
"base_PixelFlags_flag_saturatedCenter")
or 942 s.get(
"base_PixelFlags_flag_crCenter")):
944 elif (s.get(
"base_PixelFlags_flag_interpolated")
or 945 s.get(
"base_PixelFlags_flag_saturated")
or 946 s.get(
"base_PixelFlags_flag_cr")):
950 disp.dot(ptype, x, y, size=4, ctype=ctype)
953 if display
and showPixelResiduals
and selectSources:
954 nonKernelSources = []
955 for source
in selectSources:
956 if source
not in kernelSources:
957 nonKernelSources.append(source)
959 diUtils.plotPixelResiduals(exposure,
960 subtractRes.warpedExposure,
961 subtractRes.subtractedExposure,
962 subtractRes.kernelCellSet,
963 subtractRes.psfMatchingKernel,
964 subtractRes.backgroundModel,
966 self.subtract.config.kernel.active.detectionConfig,
968 diUtils.plotPixelResiduals(exposure,
969 subtractRes.warpedExposure,
970 subtractRes.subtractedExposure,
971 subtractRes.kernelCellSet,
972 subtractRes.psfMatchingKernel,
973 subtractRes.backgroundModel,
975 self.subtract.config.kernel.active.detectionConfig,
977 if display
and showDiaSources:
978 flagChecker = SourceFlagChecker(diaSources)
979 isFlagged = [flagChecker(x)
for x
in diaSources]
980 isDipole = [x.get(
"ip_diffim_ClassificationDipole_value")
for x
in diaSources]
981 diUtils.showDiaSources(diaSources, subtractRes.subtractedExposure, isFlagged, isDipole,
982 frame=lsstDebug.frame)
985 if display
and showDipoles:
986 DipoleAnalysis().displayDipoles(subtractRes.subtractedExposure, diaSources,
987 frame=lsstDebug.frame)
990 def _getConfigName(self):
991 """Return the name of the config dataset 993 return "%sDiff_config" % (self.config.coaddName,)
995 def _getMetadataName(self):
996 """Return the name of the metadata dataset 998 return "%sDiff_metadata" % (self.config.coaddName,)
1001 """Return a dict of empty catalogs for each catalog dataset produced by this task.""" 1004 return {self.config.coaddName +
"Diff_diaSrc": diaSrc}
1007 def _makeArgumentParser(cls):
1008 """Create an argument parser 1010 parser = pipeBase.ArgumentParser(name=cls.
_DefaultName)
1011 parser.add_id_argument(
"--id",
"calexp", help=
"data ID, e.g. --id visit=12345 ccd=1,2")
1012 parser.add_id_argument(
"--templateId",
"calexp", doMakeDataRefList=
True,
1013 help=
"Template data ID in case of calexp template," 1014 " e.g. --templateId visit=6789")
1019 winter2013WcsShift = pexConfig.Field(dtype=float, default=0.0,
1020 doc=
"Shift stars going into RegisterTask by this amount")
1021 winter2013WcsRms = pexConfig.Field(dtype=float, default=0.0,
1022 doc=
"Perturb stars going into RegisterTask by this amount")
1025 ImageDifferenceConfig.setDefaults(self)
1026 self.
getTemplate.retarget(GetCalexpAsTemplateTask)
1030 """!Image difference Task used in the Winter 2013 data challege. 1031 Enables testing the effects of registration shifts and scatter. 1033 For use with winter 2013 simulated images: 1034 Use --templateId visit=88868666 for sparse data 1035 --templateId visit=22222200 for dense data (g) 1036 --templateId visit=11111100 for dense data (i) 1038 ConfigClass = Winter2013ImageDifferenceConfig
1039 _DefaultName =
"winter2013ImageDifference" 1042 ImageDifferenceTask.__init__(self, **kwargs)
1045 """Fit the relative astrometry between templateSources and selectSources""" 1046 if self.config.winter2013WcsShift > 0.0:
1048 self.config.winter2013WcsShift)
1049 cKey = templateSources[0].getTable().getCentroidKey()
1050 for source
in templateSources:
1051 centroid = source.get(cKey)
1052 source.set(cKey, centroid + offset)
1053 elif self.config.winter2013WcsRms > 0.0:
1054 cKey = templateSources[0].getTable().getCentroidKey()
1055 for source
in templateSources:
1056 offset =
geom.Extent2D(self.config.winter2013WcsRms*numpy.random.normal(),
1057 self.config.winter2013WcsRms*numpy.random.normal())
1058 centroid = source.get(cKey)
1059 source.set(cKey, centroid + offset)
1061 results = self.register.
run(templateSources, templateExposure.getWcs(),
1062 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.
def makeIdFactory(expId, expBits)
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)
def run(self, exposure=None, selectSources=None, templateExposure=None, templateSources=None, idFactory=None, calexpBackgroundExposure=None, subtractedExposure=None)