26 import lsst.pex.config
as pexConfig
33 from lsst.meas.base import ForcedMeasurementTask, EvaluateLocalCalibrationTask
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=
False,
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 doEvalLocCalibration = pexConfig.Field(
86 doc=
"Store calibration products (local wcs and photoCalib) in the " 87 "output DiaSource catalog.")
88 doDipoleFitting = pexConfig.Field(dtype=bool, default=
True, doc=
"Measure dipoles using new algorithm?")
89 doForcedMeasurement = pexConfig.Field(
92 doc=
"Force photometer diaSource locations on PVI?")
93 doWriteSubtractedExp = pexConfig.Field(dtype=bool, default=
True, doc=
"Write difference exposure?")
94 doWriteMatchedExp = pexConfig.Field(dtype=bool, default=
False,
95 doc=
"Write warped and PSF-matched template coadd exposure?")
96 doWriteSources = pexConfig.Field(dtype=bool, default=
True, doc=
"Write sources?")
97 doAddMetrics = pexConfig.Field(dtype=bool, default=
True,
98 doc=
"Add columns to the source table to hold analysis metrics?")
100 coaddName = pexConfig.Field(
101 doc=
"coadd name: typically one of deep, goodSeeing, or dcr",
105 convolveTemplate = pexConfig.Field(
106 doc=
"Which image gets convolved (default = template)",
110 refObjLoader = pexConfig.ConfigurableField(
111 target=LoadIndexedReferenceObjectsTask,
112 doc=
"reference object loader",
114 astrometer = pexConfig.ConfigurableField(
115 target=AstrometryTask,
116 doc=
"astrometry task; used to match sources to reference objects, but not to fit a WCS",
118 sourceSelector = pexConfig.ConfigurableField(
119 target=ObjectSizeStarSelectorTask,
120 doc=
"Source selection algorithm",
122 subtract = subtractAlgorithmRegistry.makeField(
"Subtraction Algorithm", default=
"al")
123 decorrelate = pexConfig.ConfigurableField(
124 target=DecorrelateALKernelSpatialTask,
125 doc=
"Decorrelate effects of A&L kernel convolution on image difference, only if doSubtract is True. " 126 "If this option is enabled, then detection.thresholdValue should be set to 5.0 (rather than the " 129 doSpatiallyVarying = pexConfig.Field(
132 doc=
"If using Zogy or A&L decorrelation, perform these on a grid across the " 133 "image in order to allow for spatial variations" 135 detection = pexConfig.ConfigurableField(
136 target=SourceDetectionTask,
137 doc=
"Low-threshold detection for final measurement",
139 measurement = pexConfig.ConfigurableField(
140 target=DipoleFitTask,
141 doc=
"Enable updated dipole fitting method",
143 evalLocCalib = pexConfig.ConfigurableField(
144 target=EvaluateLocalCalibrationTask,
145 doc=
"Task to strip calibrations from an exposure and store their " 146 "local values in the output DiaSource catalog." 148 forcedMeasurement = pexConfig.ConfigurableField(
149 target=ForcedMeasurementTask,
150 doc=
"Subtask to force photometer PVI at diaSource location.",
152 getTemplate = pexConfig.ConfigurableField(
153 target=GetCoaddAsTemplateTask,
154 doc=
"Subtask to retrieve template exposure and sources",
156 controlStepSize = pexConfig.Field(
157 doc=
"What step size (every Nth one) to select a control sample from the kernelSources",
161 controlRandomSeed = pexConfig.Field(
162 doc=
"Random seed for shuffing the control sample",
166 register = pexConfig.ConfigurableField(
168 doc=
"Task to enable image-to-image image registration (warping)",
170 kernelSourcesFromRef = pexConfig.Field(
171 doc=
"Select sources to measure kernel from reference catalog if True, template if false",
175 templateSipOrder = pexConfig.Field(dtype=int, default=2,
176 doc=
"Sip Order for fitting the Template Wcs " 177 "(default is too high, overfitting)")
179 growFootprint = pexConfig.Field(dtype=int, default=2,
180 doc=
"Grow positive and negative footprints by this amount before merging")
182 diaSourceMatchRadius = pexConfig.Field(dtype=float, default=0.5,
183 doc=
"Match radius (in arcseconds) " 184 "for DiaSource to Source association")
189 self.
subtract[
'al'].kernel.name =
"AL" 190 self.
subtract[
'al'].kernel.active.fitForBackground =
True 191 self.
subtract[
'al'].kernel.active.spatialKernelOrder = 1
192 self.
subtract[
'al'].kernel.active.spatialBgOrder = 2
199 self.
detection.thresholdPolarity =
"both" 201 self.
detection.reEstimateBackground =
False 202 self.
detection.thresholdType =
"pixel_stdev" 208 self.
measurement.algorithms.names.add(
'base_PeakLikelihoodFlux')
212 "id":
"objectId",
"parent":
"parentObjectId",
"coord_ra":
"coord_ra",
"coord_dec":
"coord_dec"}
220 pexConfig.Config.validate(self)
222 raise ValueError(
"Cannot run source measurement without source detection.")
224 raise ValueError(
"Cannot run source merging without source detection.")
226 raise ValueError(
"doUseRegister=True and doSelectSources=False. " 227 "Cannot run RegisterTask without selecting sources.")
230 raise ValueError(
"Mis-matched coaddName and getTemplate.coaddName in the config.")
237 return pipeBase.TaskRunner.getTargetList(parsedCmd, templateIdList=parsedCmd.templateId.idList,
242 """Subtract an image from a template and measure the result 244 ConfigClass = ImageDifferenceConfig
245 RunnerClass = ImageDifferenceTaskRunner
246 _DefaultName =
"imageDifference" 249 """!Construct an ImageDifference Task 251 @param[in] butler Butler object to use in constructing reference object loaders 253 pipeBase.CmdLineTask.__init__(self, **kwargs)
254 self.makeSubtask(
"getTemplate")
256 self.makeSubtask(
"subtract")
258 if self.config.subtract.name ==
'al' and self.config.doDecorrelation:
259 self.makeSubtask(
"decorrelate")
261 if self.config.doUseRegister:
262 self.makeSubtask(
"register")
263 self.
schema = afwTable.SourceTable.makeMinimalSchema()
265 if self.config.doSelectSources:
266 self.makeSubtask(
"sourceSelector")
267 if self.config.kernelSourcesFromRef:
268 self.makeSubtask(
'refObjLoader', butler=butler)
269 self.makeSubtask(
"astrometer", refObjLoader=self.refObjLoader)
272 if self.config.doDetection:
273 self.makeSubtask(
"detection", schema=self.
schema)
274 if self.config.doMeasurement:
275 self.makeSubtask(
"measurement", schema=self.
schema,
277 if self.config.doEvalLocCalibration
and self.config.doMeasurement:
278 self.makeSubtask(
"evalLocCalib", schema=self.
schema)
279 if self.config.doForcedMeasurement:
281 "ip_diffim_forced_PsfFlux_instFlux",
"D",
282 "Forced PSF flux measured on the direct image.")
284 "ip_diffim_forced_PsfFlux_instFluxErr",
"D",
285 "Forced PSF flux error measured on the direct image.")
287 "ip_diffim_forced_PsfFlux_area",
"F",
288 "Forced PSF flux effective area of PSF.",
291 "ip_diffim_forced_PsfFlux_flag",
"Flag",
292 "Forced PSF flux general failure flag.")
294 "ip_diffim_forced_PsfFlux_flag_noGoodPixels",
"Flag",
295 "Forced PSF flux not enough non-rejected pixels in data to attempt the fit.")
297 "ip_diffim_forced_PsfFlux_flag_edge",
"Flag",
298 "Forced PSF flux object was too close to the edge of the image to use the full PSF model.")
299 self.makeSubtask(
"forcedMeasurement", refSchema=self.
schema)
300 if self.config.doMatchSources:
301 self.
schema.addField(
"refMatchId",
"L",
"unique id of reference catalog match")
302 self.
schema.addField(
"srcMatchId",
"L",
"unique id of source match")
306 """Subtract an image from a template coadd and measure the result 309 - warp template coadd to match WCS of image 310 - PSF match image to warped template 311 - subtract image from PSF-matched, warped template 312 - persist difference image 316 @param sensorRef: sensor-level butler data reference, used for the following data products: 322 - self.config.coaddName + "Coadd_skyMap" 323 - self.config.coaddName + "Coadd" 324 Input or output, depending on config: 325 - self.config.coaddName + "Diff_subtractedExp" 326 Output, depending on config: 327 - self.config.coaddName + "Diff_matchedExp" 328 - self.config.coaddName + "Diff_src" 330 @return pipe_base Struct containing these fields: 331 - subtractedExposure: exposure after subtracting template; 332 the unpersisted version if subtraction not run but detection run 333 None if neither subtraction nor detection run (i.e. nothing useful done) 334 - subtractRes: results of subtraction task; None if subtraction not run 335 - sources: detected and possibly measured sources; None if detection not run 337 self.log.
info(
"Processing %s" % (sensorRef.dataId))
340 subtractedExposure =
None 344 controlSources =
None 350 expBits = sensorRef.get(
"ccdExposureId_bits")
351 expId = int(sensorRef.get(
"ccdExposureId"))
352 idFactory = afwTable.IdFactory.makeSource(expId, 64 - expBits)
355 exposure = sensorRef.get(
"calexp", immediate=
True)
356 if self.config.doAddCalexpBackground:
357 mi = exposure.getMaskedImage()
358 mi += sensorRef.get(
"calexpBackground").getImage()
359 if not exposure.hasPsf():
360 raise pipeBase.TaskError(
"Exposure has no psf")
361 sciencePsf = exposure.getPsf()
363 subtractedExposureName = self.config.coaddName +
"Diff_differenceExp" 364 templateExposure =
None 365 templateSources =
None 366 if self.config.doSubtract:
367 template = self.getTemplate.
run(exposure, sensorRef, templateIdList=templateIdList)
368 templateExposure = template.exposure
369 templateSources = template.sources
371 if self.config.subtract.name ==
'zogy':
372 subtractRes = self.subtract.subtractExposures(templateExposure, exposure,
374 spatiallyVarying=self.config.doSpatiallyVarying,
375 doPreConvolve=self.config.doPreConvolve)
376 subtractedExposure = subtractRes.subtractedExposure
378 elif self.config.subtract.name ==
'al':
380 scienceSigmaOrig = sciencePsf.computeShape().getDeterminantRadius()
383 templateSigma = templateExposure.getPsf().computeShape().getDeterminantRadius()
390 if self.config.doPreConvolve:
393 srcMI = exposure.getMaskedImage()
394 destMI = srcMI.Factory(srcMI.getDimensions())
396 if self.config.useGaussianForPreConvolution:
398 kWidth, kHeight = sciencePsf.getLocalKernel().getDimensions()
404 exposure.setMaskedImage(destMI)
405 scienceSigmaPost = scienceSigmaOrig*math.sqrt(2)
407 scienceSigmaPost = scienceSigmaOrig
410 if self.config.doSelectSources:
411 if not sensorRef.datasetExists(
"src"):
412 self.log.
warn(
"Src product does not exist; running detection, measurement, selection")
414 selectSources = self.subtract.getSelectSources(
416 sigma=scienceSigmaPost,
417 doSmooth=
not self.doPreConvolve,
421 self.log.
info(
"Source selection via src product")
423 selectSources = sensorRef.get(
"src")
427 referenceFwhmPix=scienceSigmaPost*FwhmPerSigma,
428 targetFwhmPix=templateSigma*FwhmPerSigma))
430 if self.config.doAddMetrics:
432 kcQa = KernelCandidateQa(nparam)
433 selectSources = kcQa.addToSchema(selectSources)
435 if self.config.kernelSourcesFromRef:
437 astromRet = self.astrometer.loadAndMatch(exposure=exposure, sourceCat=selectSources)
438 matches = astromRet.matches
439 elif templateSources:
442 mc.findOnlyClosest =
False 446 raise RuntimeError(
"doSelectSources=True and kernelSourcesFromRef=False," 447 "but template sources not available. Cannot match science " 448 "sources with template sources. Run process* on data from " 449 "which templates are built.")
451 kernelSources = self.sourceSelector.
run(selectSources, exposure=exposure,
452 matches=matches).sourceCat
454 random.shuffle(kernelSources, random.random)
455 controlSources = kernelSources[::self.config.controlStepSize]
456 kernelSources = [k
for i, k
in enumerate(kernelSources)
457 if i % self.config.controlStepSize]
459 if self.config.doSelectDcrCatalog:
460 redSelector = DiaCatalogSourceSelectorTask(
461 DiaCatalogSourceSelectorConfig(grMin=self.sourceSelector.config.grMax,
463 redSources = redSelector.selectStars(exposure, selectSources, matches=matches).starCat
464 controlSources.extend(redSources)
466 blueSelector = DiaCatalogSourceSelectorTask(
467 DiaCatalogSourceSelectorConfig(grMin=-99.999,
468 grMax=self.sourceSelector.config.grMin))
469 blueSources = blueSelector.selectStars(exposure, selectSources,
470 matches=matches).starCat
471 controlSources.extend(blueSources)
473 if self.config.doSelectVariableCatalog:
474 varSelector = DiaCatalogSourceSelectorTask(
475 DiaCatalogSourceSelectorConfig(includeVariable=
True))
476 varSources = varSelector.selectStars(exposure, selectSources, matches=matches).starCat
477 controlSources.extend(varSources)
479 self.log.
info(
"Selected %d / %d sources for Psf matching (%d for control sample)" 480 % (len(kernelSources), len(selectSources), len(controlSources)))
482 if self.config.doUseRegister:
483 self.log.
info(
"Registering images")
485 if templateSources
is None:
488 templateSources = self.subtract.getSelectSources(
497 wcsResults = self.
fitAstrometry(templateSources, templateExposure, selectSources)
498 warpedExp = self.register.
warpExposure(templateExposure, wcsResults.wcs,
499 exposure.getWcs(), exposure.getBBox())
500 templateExposure = warpedExp
505 if self.config.doDebugRegister:
507 srcToMatch = {x.second.getId(): x.first
for x
in matches}
509 refCoordKey = wcsResults.matches[0].first.getTable().getCoordKey()
510 inCentroidKey = wcsResults.matches[0].second.getTable().getCentroidKey()
511 sids = [m.first.getId()
for m
in wcsResults.matches]
512 positions = [m.first.get(refCoordKey)
for m
in wcsResults.matches]
513 residuals = [m.first.get(refCoordKey).getOffsetFrom(wcsResults.wcs.pixelToSky(
514 m.second.get(inCentroidKey)))
for m
in wcsResults.matches]
515 allresids = dict(zip(sids, zip(positions, residuals)))
517 cresiduals = [m.first.get(refCoordKey).getTangentPlaneOffset(
518 wcsResults.wcs.pixelToSky(
519 m.second.get(inCentroidKey)))
for m
in wcsResults.matches]
520 colors = numpy.array([-2.5*numpy.log10(srcToMatch[x].get(
"g")) +
521 2.5*numpy.log10(srcToMatch[x].get(
"r")) 522 for x
in sids
if x
in srcToMatch.keys()])
523 dlong = numpy.array([r[0].asArcseconds()
for s, r
in zip(sids, cresiduals)
524 if s
in srcToMatch.keys()])
525 dlat = numpy.array([r[1].asArcseconds()
for s, r
in zip(sids, cresiduals)
526 if s
in srcToMatch.keys()])
527 idx1 = numpy.where(colors < self.sourceSelector.config.grMin)
528 idx2 = numpy.where((colors >= self.sourceSelector.config.grMin) &
529 (colors <= self.sourceSelector.config.grMax))
530 idx3 = numpy.where(colors > self.sourceSelector.config.grMax)
531 rms1Long = IqrToSigma*(
532 (numpy.percentile(dlong[idx1], 75) - numpy.percentile(dlong[idx1], 25)))
533 rms1Lat = IqrToSigma*(numpy.percentile(dlat[idx1], 75) -
534 numpy.percentile(dlat[idx1], 25))
535 rms2Long = IqrToSigma*(
536 (numpy.percentile(dlong[idx2], 75) - numpy.percentile(dlong[idx2], 25)))
537 rms2Lat = IqrToSigma*(numpy.percentile(dlat[idx2], 75) -
538 numpy.percentile(dlat[idx2], 25))
539 rms3Long = IqrToSigma*(
540 (numpy.percentile(dlong[idx3], 75) - numpy.percentile(dlong[idx3], 25)))
541 rms3Lat = IqrToSigma*(numpy.percentile(dlat[idx3], 75) -
542 numpy.percentile(dlat[idx3], 25))
543 self.log.
info(
"Blue star offsets'': %.3f %.3f, %.3f %.3f" %
544 (numpy.median(dlong[idx1]), rms1Long,
545 numpy.median(dlat[idx1]), rms1Lat))
546 self.log.
info(
"Green star offsets'': %.3f %.3f, %.3f %.3f" %
547 (numpy.median(dlong[idx2]), rms2Long,
548 numpy.median(dlat[idx2]), rms2Lat))
549 self.log.
info(
"Red star offsets'': %.3f %.3f, %.3f %.3f" %
550 (numpy.median(dlong[idx3]), rms3Long,
551 numpy.median(dlat[idx3]), rms3Lat))
553 self.metadata.add(
"RegisterBlueLongOffsetMedian", numpy.median(dlong[idx1]))
554 self.metadata.add(
"RegisterGreenLongOffsetMedian", numpy.median(dlong[idx2]))
555 self.metadata.add(
"RegisterRedLongOffsetMedian", numpy.median(dlong[idx3]))
556 self.metadata.add(
"RegisterBlueLongOffsetStd", rms1Long)
557 self.metadata.add(
"RegisterGreenLongOffsetStd", rms2Long)
558 self.metadata.add(
"RegisterRedLongOffsetStd", rms3Long)
560 self.metadata.add(
"RegisterBlueLatOffsetMedian", numpy.median(dlat[idx1]))
561 self.metadata.add(
"RegisterGreenLatOffsetMedian", numpy.median(dlat[idx2]))
562 self.metadata.add(
"RegisterRedLatOffsetMedian", numpy.median(dlat[idx3]))
563 self.metadata.add(
"RegisterBlueLatOffsetStd", rms1Lat)
564 self.metadata.add(
"RegisterGreenLatOffsetStd", rms2Lat)
565 self.metadata.add(
"RegisterRedLatOffsetStd", rms3Lat)
572 self.log.
info(
"Subtracting images")
573 subtractRes = self.subtract.subtractExposures(
574 templateExposure=templateExposure,
575 scienceExposure=exposure,
576 candidateList=kernelSources,
577 convolveTemplate=self.config.convolveTemplate,
578 doWarping=
not self.config.doUseRegister
580 subtractedExposure = subtractRes.subtractedExposure
582 if self.config.doWriteMatchedExp:
583 sensorRef.put(subtractRes.matchedExposure, self.config.coaddName +
"Diff_matchedExp")
585 if self.config.doDetection:
586 self.log.
info(
"Computing diffim PSF")
587 if subtractedExposure
is None:
588 subtractedExposure = sensorRef.get(subtractedExposureName)
591 if not subtractedExposure.hasPsf():
592 if self.config.convolveTemplate:
593 subtractedExposure.setPsf(exposure.getPsf())
595 if templateExposure
is None:
596 template = self.getTemplate.
run(exposure, sensorRef,
597 templateIdList=templateIdList)
598 subtractedExposure.setPsf(template.exposure.getPsf())
603 if self.config.doDecorrelation
and self.config.doSubtract:
605 if preConvPsf
is not None:
606 preConvKernel = preConvPsf.getLocalKernel()
607 decorrResult = self.decorrelate.
run(exposure, templateExposure,
609 subtractRes.psfMatchingKernel,
610 spatiallyVarying=self.config.doSpatiallyVarying,
611 preConvKernel=preConvKernel)
612 subtractedExposure = decorrResult.correctedExposure
616 if self.config.doDetection:
617 self.log.
info(
"Running diaSource detection")
619 mask = subtractedExposure.getMaskedImage().getMask()
620 mask &= ~(mask.getPlaneBitMask(
"DETECTED") | mask.getPlaneBitMask(
"DETECTED_NEGATIVE"))
622 table = afwTable.SourceTable.make(self.
schema, idFactory)
624 results = self.detection.makeSourceCatalog(
626 exposure=subtractedExposure,
627 doSmooth=
not self.config.doPreConvolve
630 if self.config.doMerge:
631 fpSet = results.fpSets.positive
632 fpSet.merge(results.fpSets.negative, self.config.growFootprint,
633 self.config.growFootprint,
False)
635 fpSet.makeSources(diaSources)
636 self.log.
info(
"Merging detections into %d sources" % (len(diaSources)))
638 diaSources = results.sources
640 if self.config.doMeasurement:
641 newDipoleFitting = self.config.doDipoleFitting
642 self.log.
info(
"Running diaSource measurement: newDipoleFitting=%r", newDipoleFitting)
643 if not newDipoleFitting:
645 self.measurement.
run(diaSources, subtractedExposure)
648 if self.config.doSubtract
and 'matchedExposure' in subtractRes.getDict():
649 self.measurement.
run(diaSources, subtractedExposure, exposure,
650 subtractRes.matchedExposure)
652 self.measurement.
run(diaSources, subtractedExposure, exposure)
654 if self.config.doEvalLocCalibration
and self.config.doMeasurement:
655 self.evalLocCalib.
run(diaSources, subtractedExposure)
657 if self.config.doForcedMeasurement:
660 forcedSources = self.forcedMeasurement.generateMeasCat(
661 exposure, diaSources, subtractedExposure.getWcs())
662 self.forcedMeasurement.
run(forcedSources, exposure, diaSources, subtractedExposure.getWcs())
664 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_instFlux")[0],
665 "ip_diffim_forced_PsfFlux_instFlux",
True)
666 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_instFluxErr")[0],
667 "ip_diffim_forced_PsfFlux_instFluxErr",
True)
668 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_area")[0],
669 "ip_diffim_forced_PsfFlux_area",
True)
670 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_flag")[0],
671 "ip_diffim_forced_PsfFlux_flag",
True)
672 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_flag_noGoodPixels")[0],
673 "ip_diffim_forced_PsfFlux_flag_noGoodPixels",
True)
674 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_flag_edge")[0],
675 "ip_diffim_forced_PsfFlux_flag_edge",
True)
676 for diaSource, forcedSource
in zip(diaSources, forcedSources):
677 diaSource.assign(forcedSource, mapper)
680 if self.config.doMatchSources:
681 if sensorRef.datasetExists(
"src"):
683 matchRadAsec = self.config.diaSourceMatchRadius
684 matchRadPixel = matchRadAsec/exposure.getWcs().pixelScale().asArcseconds()
686 srcMatches =
afwTable.matchXy(sensorRef.get(
"src"), diaSources, matchRadPixel)
687 srcMatchDict = dict([(srcMatch.second.getId(), srcMatch.first.getId())
for 688 srcMatch
in srcMatches])
689 self.log.
info(
"Matched %d / %d diaSources to sources" % (len(srcMatchDict),
692 self.log.
warn(
"Src product does not exist; cannot match with diaSources")
697 refAstromConfig.matcher.maxMatchDistArcSec = matchRadAsec
699 astromRet = refAstrometer.run(exposure=exposure, sourceCat=diaSources)
700 refMatches = astromRet.matches
701 if refMatches
is None:
702 self.log.
warn(
"No diaSource matches with reference catalog")
705 self.log.
info(
"Matched %d / %d diaSources to reference catalog" % (len(refMatches),
707 refMatchDict = dict([(refMatch.second.getId(), refMatch.first.getId())
for 708 refMatch
in refMatches])
711 for diaSource
in diaSources:
712 sid = diaSource.getId()
713 if sid
in srcMatchDict:
714 diaSource.set(
"srcMatchId", srcMatchDict[sid])
715 if sid
in refMatchDict:
716 diaSource.set(
"refMatchId", refMatchDict[sid])
718 if diaSources
is not None and self.config.doWriteSources:
719 sensorRef.put(diaSources, self.config.coaddName +
"Diff_diaSrc")
721 if self.config.doAddMetrics
and self.config.doSelectSources:
722 self.log.
info(
"Evaluating metrics and control sample")
725 for cell
in subtractRes.kernelCellSet.getCellList():
726 for cand
in cell.begin(
False):
727 kernelCandList.append(cand)
730 basisList = kernelCandList[0].getKernel(KernelCandidateF.ORIG).getKernelList()
733 diffimTools.sourceTableToCandidateList(controlSources,
734 subtractRes.warpedExposure, exposure,
735 self.config.subtract.kernel.active,
736 self.config.subtract.kernel.active.detectionConfig,
737 self.log, doBuild=
True, basisList=basisList))
739 kcQa.apply(kernelCandList, subtractRes.psfMatchingKernel, subtractRes.backgroundModel,
741 kcQa.apply(controlCandList, subtractRes.psfMatchingKernel, subtractRes.backgroundModel)
743 if self.config.doDetection:
744 kcQa.aggregate(selectSources, self.metadata, allresids, diaSources)
746 kcQa.aggregate(selectSources, self.metadata, allresids)
748 sensorRef.put(selectSources, self.config.coaddName +
"Diff_kernelSrc")
750 if self.config.doWriteSubtractedExp:
751 sensorRef.put(subtractedExposure, subtractedExposureName)
753 self.
runDebug(exposure, subtractRes, selectSources, kernelSources, diaSources)
754 return pipeBase.Struct(
755 subtractedExposure=subtractedExposure,
756 subtractRes=subtractRes,
761 """Fit the relative astrometry between templateSources and selectSources 763 @todo remove this method. It originally fit a new WCS to the template before calling register.run 764 because our TAN-SIP fitter behaved badly for points far from CRPIX, but that's been fixed. 765 It remains because a subtask overrides it. 767 results = self.register.
run(templateSources, templateExposure.getWcs(),
768 templateExposure.getBBox(), selectSources)
771 def runDebug(self, exposure, subtractRes, selectSources, kernelSources, diaSources):
772 """@todo Test and update for current debug display and slot names 782 disp = afwDisplay.getDisplay(frame=lsstDebug.frame)
783 if not maskTransparency:
785 disp.setMaskTransparency(maskTransparency)
787 if display
and showSubtracted:
788 disp.mtv(subtractRes.subtractedExposure, title=
"Subtracted image")
789 mi = subtractRes.subtractedExposure.getMaskedImage()
790 x0, y0 = mi.getX0(), mi.getY0()
791 with disp.Buffering():
793 x, y = s.getX() - x0, s.getY() - y0
794 ctype =
"red" if s.get(
"flags_negative")
else "yellow" 795 if (s.get(
"base_PixelFlags_flag_interpolatedCenter")
or 796 s.get(
"base_PixelFlags_flag_saturatedCenter")
or 797 s.get(
"base_PixelFlags_flag_crCenter")):
799 elif (s.get(
"base_PixelFlags_flag_interpolated")
or 800 s.get(
"base_PixelFlags_flag_saturated")
or 801 s.get(
"base_PixelFlags_flag_cr")):
805 disp.dot(ptype, x, y, size=4, ctype=ctype)
808 if display
and showPixelResiduals
and selectSources:
809 nonKernelSources = []
810 for source
in selectSources:
811 if source
not in kernelSources:
812 nonKernelSources.append(source)
814 diUtils.plotPixelResiduals(exposure,
815 subtractRes.warpedExposure,
816 subtractRes.subtractedExposure,
817 subtractRes.kernelCellSet,
818 subtractRes.psfMatchingKernel,
819 subtractRes.backgroundModel,
821 self.subtract.config.kernel.active.detectionConfig,
823 diUtils.plotPixelResiduals(exposure,
824 subtractRes.warpedExposure,
825 subtractRes.subtractedExposure,
826 subtractRes.kernelCellSet,
827 subtractRes.psfMatchingKernel,
828 subtractRes.backgroundModel,
830 self.subtract.config.kernel.active.detectionConfig,
832 if display
and showDiaSources:
833 flagChecker = SourceFlagChecker(diaSources)
834 isFlagged = [flagChecker(x)
for x
in diaSources]
835 isDipole = [x.get(
"ip_diffim_ClassificationDipole_value")
for x
in diaSources]
836 diUtils.showDiaSources(diaSources, subtractRes.subtractedExposure, isFlagged, isDipole,
837 frame=lsstDebug.frame)
840 if display
and showDipoles:
841 DipoleAnalysis().displayDipoles(subtractRes.subtractedExposure, diaSources,
842 frame=lsstDebug.frame)
845 def _getConfigName(self):
846 """Return the name of the config dataset 848 return "%sDiff_config" % (self.config.coaddName,)
850 def _getMetadataName(self):
851 """Return the name of the metadata dataset 853 return "%sDiff_metadata" % (self.config.coaddName,)
856 """Return a dict of empty catalogs for each catalog dataset produced by this task.""" 859 return {self.config.coaddName +
"Diff_diaSrc": diaSrc}
862 def _makeArgumentParser(cls):
863 """Create an argument parser 866 parser.add_id_argument(
"--id",
"calexp", help=
"data ID, e.g. --id visit=12345 ccd=1,2")
867 parser.add_id_argument(
"--templateId",
"calexp", doMakeDataRefList=
True,
868 help=
"Template data ID in case of calexp template," 869 " e.g. --templateId visit=6789")
874 winter2013WcsShift = pexConfig.Field(dtype=float, default=0.0,
875 doc=
"Shift stars going into RegisterTask by this amount")
876 winter2013WcsRms = pexConfig.Field(dtype=float, default=0.0,
877 doc=
"Perturb stars going into RegisterTask by this amount")
880 ImageDifferenceConfig.setDefaults(self)
885 """!Image difference Task used in the Winter 2013 data challege. 886 Enables testing the effects of registration shifts and scatter. 888 For use with winter 2013 simulated images: 889 Use --templateId visit=88868666 for sparse data 890 --templateId visit=22222200 for dense data (g) 891 --templateId visit=11111100 for dense data (i) 893 ConfigClass = Winter2013ImageDifferenceConfig
894 _DefaultName =
"winter2013ImageDifference" 897 ImageDifferenceTask.__init__(self, **kwargs)
900 """Fit the relative astrometry between templateSources and selectSources""" 901 if self.config.winter2013WcsShift > 0.0:
903 self.config.winter2013WcsShift)
904 cKey = templateSources[0].getTable().getCentroidKey()
905 for source
in templateSources:
906 centroid = source.get(cKey)
907 source.set(cKey, centroid + offset)
908 elif self.config.winter2013WcsRms > 0.0:
909 cKey = templateSources[0].getTable().getCentroidKey()
910 for source
in templateSources:
911 offset =
geom.Extent2D(self.config.winter2013WcsRms*numpy.random.normal(),
912 self.config.winter2013WcsRms*numpy.random.normal())
913 centroid = source.get(cKey)
914 source.set(cKey, centroid + offset)
916 results = self.register.
run(templateSources, templateExposure.getWcs(),
917 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 run(self, skyInfo, tempExpRefList, imageScalerList, weightList, altMaskList=None, mask=None, supplementaryData=None)
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)