38 from lsst.meas.algorithms import SourceDetectionTask, SingleGaussianPsf, ObjectSizeStarSelectorTask
39 from lsst.ip.diffim import (DipoleAnalysis, SourceFlagChecker, KernelCandidateF, makeKernelBasisList,
40 KernelCandidateQa, DiaCatalogSourceSelectorTask, DiaCatalogSourceSelectorConfig,
41 GetCoaddAsTemplateTask, GetCalexpAsTemplateTask, DipoleFitTask,
42 DecorrelateALKernelSpatialTask, subtractAlgorithmRegistry)
47 from lsst.obs.base
import ExposureIdInfo
49 __all__ = [
"ImageDifferenceConfig",
"ImageDifferenceTask"]
50 FwhmPerSigma = 2*math.sqrt(2*math.log(2))
55 dimensions=(
"instrument",
"visit",
"detector",
"skymap"),
56 defaultTemplates={
"coaddName":
"deep",
61 exposure = pipeBase.connectionTypes.Input(
62 doc=
"Input science exposure to subtract from.",
63 dimensions=(
"instrument",
"visit",
"detector"),
64 storageClass=
"ExposureF",
65 name=
"{fakesType}calexp"
76 skyMap = pipeBase.connectionTypes.Input(
77 doc=
"Input definition of geometry/bbox and projection/wcs for template exposures",
78 name=BaseSkyMap.SKYMAP_DATASET_TYPE_NAME,
79 dimensions=(
"skymap", ),
80 storageClass=
"SkyMap",
82 coaddExposures = pipeBase.connectionTypes.Input(
83 doc=
"Input template to match and subtract from the exposure",
84 dimensions=(
"tract",
"patch",
"skymap",
"band"),
85 storageClass=
"ExposureF",
86 name=
"{fakesType}{coaddName}Coadd{warpTypeSuffix}",
90 dcrCoadds = pipeBase.connectionTypes.Input(
91 doc=
"Input DCR template to match and subtract from the exposure",
92 name=
"{fakesType}dcrCoadd{warpTypeSuffix}",
93 storageClass=
"ExposureF",
94 dimensions=(
"tract",
"patch",
"skymap",
"band",
"subfilter"),
98 outputSchema = pipeBase.connectionTypes.InitOutput(
99 doc=
"Schema (as an example catalog) for output DIASource catalog.",
100 storageClass=
"SourceCatalog",
101 name=
"{fakesType}{coaddName}Diff_diaSrc_schema",
104 subtractedExposure = pipeBase.connectionTypes.Output(
105 doc=
"Output AL difference or Zogy proper difference image",
106 dimensions=(
"instrument",
"visit",
"detector"),
107 storageClass=
"ExposureF",
108 name=
"{fakesType}{coaddName}Diff_differenceExp",
110 scoreExposure = pipeBase.connectionTypes.Output(
111 doc=
"Output Zogy score (likelihood) image",
112 dimensions=(
"instrument",
"visit",
"detector"),
113 storageClass=
"ExposureF",
114 name=
"{fakesType}{coaddName}Diff_scoreExp",
116 warpedExposure = pipeBase.connectionTypes.Output(
117 doc=
"Warped template used to create `subtractedExposure`.",
118 dimensions=(
"instrument",
"visit",
"detector"),
119 storageClass=
"ExposureF",
120 name=
"{fakesType}{coaddName}Diff_warpedExp",
122 matchedExposure = pipeBase.connectionTypes.Output(
123 doc=
"Warped template used to create `subtractedExposure`.",
124 dimensions=(
"instrument",
"visit",
"detector"),
125 storageClass=
"ExposureF",
126 name=
"{fakesType}{coaddName}Diff_matchedExp",
128 diaSources = pipeBase.connectionTypes.Output(
129 doc=
"Output detected diaSources on the difference image",
130 dimensions=(
"instrument",
"visit",
"detector"),
131 storageClass=
"SourceCatalog",
132 name=
"{fakesType}{coaddName}Diff_diaSrc",
135 def __init__(self, *, config=None):
136 super().__init__(config=config)
137 if config.coaddName ==
'dcr':
138 self.inputs.remove(
"coaddExposures")
140 self.inputs.remove(
"dcrCoadds")
141 if not config.doWriteSubtractedExp:
142 self.outputs.remove(
"subtractedExposure")
143 if not config.doWriteScoreExp:
144 self.outputs.remove(
"scoreExposure")
145 if not config.doWriteWarpedExp:
146 self.outputs.remove(
"warpedExposure")
147 if not config.doWriteMatchedExp:
148 self.outputs.remove(
"matchedExposure")
149 if not config.doWriteSources:
150 self.outputs.remove(
"diaSources")
156 class ImageDifferenceConfig(pipeBase.PipelineTaskConfig,
157 pipelineConnections=ImageDifferenceTaskConnections):
158 """Config for ImageDifferenceTask
160 doAddCalexpBackground = pexConfig.Field(dtype=bool, default=
False,
161 doc=
"Add background to calexp before processing it. "
162 "Useful as ipDiffim does background matching.")
163 doUseRegister = pexConfig.Field(dtype=bool, default=
False,
164 doc=
"Re-compute astrometry on the template. "
165 "Use image-to-image registration to align template with "
166 "science image (AL only).")
167 doDebugRegister = pexConfig.Field(dtype=bool, default=
False,
168 doc=
"Writing debugging data for doUseRegister")
169 doSelectSources = pexConfig.Field(dtype=bool, default=
False,
170 doc=
"Select stars to use for kernel fitting (AL only)")
171 doSelectDcrCatalog = pexConfig.Field(dtype=bool, default=
False,
172 doc=
"Select stars of extreme color as part "
173 "of the control sample (AL only)")
174 doSelectVariableCatalog = pexConfig.Field(dtype=bool, default=
False,
175 doc=
"Select stars that are variable to be part "
176 "of the control sample (AL only)")
177 doSubtract = pexConfig.Field(dtype=bool, default=
True, doc=
"Compute subtracted exposure?")
178 doPreConvolve = pexConfig.Field(dtype=bool, default=
False,
179 doc=
"Convolve science image by its PSF before PSF-matching (AL only)."
180 " The difference image becomes a likelihood image.")
181 useScoreImageDetection = pexConfig.Field(
182 dtype=bool, default=
False, doc=
"Calculate and detect sources on the Zogy score image (Zogy only).")
183 doWriteScoreExp = pexConfig.Field(
184 dtype=bool, default=
False, doc=
"Write score exposure (Zogy only) ?")
185 doScaleTemplateVariance = pexConfig.Field(dtype=bool, default=
False,
186 doc=
"Scale variance of the template before PSF matching")
187 doScaleDiffimVariance = pexConfig.Field(dtype=bool, default=
True,
188 doc=
"Scale variance of the diffim before PSF matching. "
189 "You may do either this or template variance scaling, "
190 "or neither. (Doing both is a waste of CPU.)")
191 useGaussianForPreConvolution = pexConfig.Field(dtype=bool, default=
True,
192 doc=
"Use a simple gaussian PSF model for pre-convolution "
193 "(else use fit PSF)? Ignored if doPreConvolve false.")
194 doDetection = pexConfig.Field(dtype=bool, default=
True, doc=
"Detect sources?")
195 doDecorrelation = pexConfig.Field(dtype=bool, default=
True,
196 doc=
"Perform diffim decorrelation to undo pixel correlation due to A&L "
197 "kernel convolution (AL only)? If True, also update the diffim PSF.")
198 doMerge = pexConfig.Field(dtype=bool, default=
True,
199 doc=
"Merge positive and negative diaSources with grow radius "
200 "set by growFootprint")
201 doMatchSources = pexConfig.Field(dtype=bool, default=
False,
202 doc=
"Match diaSources with input calexp sources and ref catalog sources")
203 doMeasurement = pexConfig.Field(dtype=bool, default=
True, doc=
"Measure diaSources?")
204 doDipoleFitting = pexConfig.Field(dtype=bool, default=
True, doc=
"Measure dipoles using new algorithm?")
205 doForcedMeasurement = pexConfig.Field(
208 doc=
"Force photometer diaSource locations on PVI?")
209 doWriteSubtractedExp = pexConfig.Field(
210 dtype=bool, default=
True, doc=
"Write difference exposure (AL and Zogy) ?")
211 doWriteWarpedExp = pexConfig.Field(
212 dtype=bool, default=
False, doc=
"Write WCS, warped template coadd exposure?")
213 doWriteMatchedExp = pexConfig.Field(dtype=bool, default=
False,
214 doc=
"Write warped and PSF-matched template coadd exposure?")
215 doWriteSources = pexConfig.Field(dtype=bool, default=
True, doc=
"Write sources?")
216 doAddMetrics = pexConfig.Field(dtype=bool, default=
False,
217 doc=
"Add columns to the source table to hold analysis metrics?")
219 coaddName = pexConfig.Field(
220 doc=
"coadd name: typically one of deep, goodSeeing, or dcr",
224 convolveTemplate = pexConfig.Field(
225 doc=
"Which image gets convolved (default = template)",
229 refObjLoader = pexConfig.ConfigurableField(
230 target=LoadIndexedReferenceObjectsTask,
231 doc=
"reference object loader",
233 astrometer = pexConfig.ConfigurableField(
234 target=AstrometryTask,
235 doc=
"astrometry task; used to match sources to reference objects, but not to fit a WCS",
237 sourceSelector = pexConfig.ConfigurableField(
238 target=ObjectSizeStarSelectorTask,
239 doc=
"Source selection algorithm",
241 subtract = subtractAlgorithmRegistry.makeField(
"Subtraction Algorithm", default=
"al")
242 decorrelate = pexConfig.ConfigurableField(
243 target=DecorrelateALKernelSpatialTask,
244 doc=
"Decorrelate effects of A&L kernel convolution on image difference, only if doSubtract is True. "
245 "If this option is enabled, then detection.thresholdValue should be set to 5.0 (rather than the "
249 doSpatiallyVarying = pexConfig.Field(
252 doc=
"Perform A&L decorrelation on a grid across the "
253 "image in order to allow for spatial variations. Zogy does not use this option."
255 detection = pexConfig.ConfigurableField(
256 target=SourceDetectionTask,
257 doc=
"Low-threshold detection for final measurement",
259 measurement = pexConfig.ConfigurableField(
260 target=DipoleFitTask,
261 doc=
"Enable updated dipole fitting method",
263 doApCorr = lsst.pex.config.Field(
266 doc=
"Run subtask to apply aperture corrections"
268 applyApCorr = lsst.pex.config.ConfigurableField(
269 target=ApplyApCorrTask,
270 doc=
"Subtask to apply aperture corrections"
272 forcedMeasurement = pexConfig.ConfigurableField(
273 target=ForcedMeasurementTask,
274 doc=
"Subtask to force photometer PVI at diaSource location.",
276 getTemplate = pexConfig.ConfigurableField(
277 target=GetCoaddAsTemplateTask,
278 doc=
"Subtask to retrieve template exposure and sources",
280 scaleVariance = pexConfig.ConfigurableField(
281 target=ScaleVarianceTask,
282 doc=
"Subtask to rescale the variance of the template "
283 "to the statistically expected level"
285 controlStepSize = pexConfig.Field(
286 doc=
"What step size (every Nth one) to select a control sample from the kernelSources",
290 controlRandomSeed = pexConfig.Field(
291 doc=
"Random seed for shuffing the control sample",
295 register = pexConfig.ConfigurableField(
297 doc=
"Task to enable image-to-image image registration (warping)",
299 kernelSourcesFromRef = pexConfig.Field(
300 doc=
"Select sources to measure kernel from reference catalog if True, template if false",
304 templateSipOrder = pexConfig.Field(
305 dtype=int, default=2,
306 doc=
"Sip Order for fitting the Template Wcs (default is too high, overfitting)"
308 growFootprint = pexConfig.Field(
309 dtype=int, default=2,
310 doc=
"Grow positive and negative footprints by this amount before merging"
312 diaSourceMatchRadius = pexConfig.Field(
313 dtype=float, default=0.5,
314 doc=
"Match radius (in arcseconds) for DiaSource to Source association"
316 requiredTemplateFraction = pexConfig.Field(
317 dtype=float, default=0.1,
318 doc=
"Do not attempt to run task if template covers less than this fraction of pixels."
319 "Setting to 0 will always attempt image subtraction"
321 doSkySources = pexConfig.Field(
324 doc=
"Generate sky sources?",
326 skySources = pexConfig.ConfigurableField(
327 target=SkyObjectsTask,
328 doc=
"Generate sky sources",
331 def setDefaults(self):
334 self.subtract[
'al'].kernel.name =
"AL"
335 self.subtract[
'al'].kernel.active.fitForBackground =
True
336 self.subtract[
'al'].kernel.active.spatialKernelOrder = 1
337 self.subtract[
'al'].kernel.active.spatialBgOrder = 2
340 self.detection.thresholdPolarity =
"both"
341 self.detection.thresholdValue = 5.0
342 self.detection.reEstimateBackground =
False
343 self.detection.thresholdType =
"pixel_stdev"
349 self.measurement.algorithms.names.add(
'base_PeakLikelihoodFlux')
350 self.measurement.plugins.names |= [
'base_LocalPhotoCalib',
353 self.forcedMeasurement.plugins = [
"base_TransformedCentroid",
"base_PsfFlux"]
354 self.forcedMeasurement.copyColumns = {
355 "id":
"objectId",
"parent":
"parentObjectId",
"coord_ra":
"coord_ra",
"coord_dec":
"coord_dec"}
356 self.forcedMeasurement.slots.centroid =
"base_TransformedCentroid"
357 self.forcedMeasurement.slots.shape =
None
360 random.seed(self.controlRandomSeed)
363 pexConfig.Config.validate(self)
364 if not self.doSubtract
and not self.doDetection:
365 raise ValueError(
"Either doSubtract or doDetection must be enabled.")
366 if self.doMeasurement
and not self.doDetection:
367 raise ValueError(
"Cannot run source measurement without source detection.")
368 if self.doMerge
and not self.doDetection:
369 raise ValueError(
"Cannot run source merging without source detection.")
370 if self.doSkySources
and not self.doDetection:
371 raise ValueError(
"Cannot run sky source creation without source detection.")
372 if self.doUseRegister
and not self.doSelectSources:
373 raise ValueError(
"doUseRegister=True and doSelectSources=False. "
374 "Cannot run RegisterTask without selecting sources.")
375 if hasattr(self.getTemplate,
"coaddName"):
376 if self.getTemplate.coaddName != self.coaddName:
377 raise ValueError(
"Mis-matched coaddName and getTemplate.coaddName in the config.")
378 if self.doScaleDiffimVariance
and self.doScaleTemplateVariance:
379 raise ValueError(
"Scaling the diffim variance and scaling the template variance "
380 "are both set. Please choose one or the other.")
382 if self.subtract.name ==
'zogy':
383 if self.doWriteScoreExp
and not self.useScoreImageDetection:
384 raise ValueError(
"doWriteScoreExp=True and useScoreImageDetection=False "
385 "is not supported. Score image is not calculated.")
386 if self.doWriteMatchedExp:
387 raise ValueError(
"doWriteMatchedExp=True Matched exposure is not "
388 "calculated in zogy subtraction.")
389 if self.doAddMetrics:
390 raise ValueError(
"doAddMetrics=True Kernel metrics does not exist in zogy subtraction.")
391 if self.doDecorrelation:
393 "doDecorrelation=True The decorrelation afterburner does not exist in zogy subtraction.")
394 if self.doPreConvolve:
396 "doPreConvolve=True Pre-convolution is not a zogy option.")
397 if self.doSelectSources:
399 "doSelectSources=True Selecting sources for PSF matching is not a zogy option.")
401 if self.useScoreImageDetection:
402 raise ValueError(
"useScoreImageDetection=True Score exposure does not "
403 "exist in AL subtraction.")
404 if self.doWriteScoreExp:
405 raise ValueError(
"doWriteScoreExp=True Score exposure does not exist in AL subtraction.")
406 if self.doAddMetrics
and not self.doSubtract:
407 raise ValueError(
"Subtraction must be enabled for kernel metrics calculation.")
408 if self.doPreConvolve
and self.doDecorrelation:
409 raise NotImplementedError(
410 "doPreConvolve=True and doDecorrelation=True "
411 "The decorrelation afterburner cannot handle pre-convolved exposures.")
414 class ImageDifferenceTaskRunner(pipeBase.ButlerInitializedTaskRunner):
417 def getTargetList(parsedCmd, **kwargs):
418 return pipeBase.TaskRunner.getTargetList(parsedCmd, templateIdList=parsedCmd.templateId.idList,
422 class ImageDifferenceTask(pipeBase.CmdLineTask, pipeBase.PipelineTask):
423 """Subtract an image from a template and measure the result
425 ConfigClass = ImageDifferenceConfig
426 RunnerClass = ImageDifferenceTaskRunner
427 _DefaultName =
"imageDifference"
429 def __init__(self, butler=None, **kwargs):
430 """!Construct an ImageDifference Task
432 @param[in] butler Butler object to use in constructing reference object loaders
434 super().__init__(**kwargs)
435 self.makeSubtask(
"getTemplate")
437 self.makeSubtask(
"subtract")
439 if self.config.subtract.name ==
'al' and self.config.doDecorrelation:
440 self.makeSubtask(
"decorrelate")
442 if self.config.doScaleTemplateVariance
or self.config.doScaleDiffimVariance:
443 self.makeSubtask(
"scaleVariance")
445 if self.config.doUseRegister:
446 self.makeSubtask(
"register")
447 self.schema = afwTable.SourceTable.makeMinimalSchema()
449 if self.config.doSelectSources:
450 self.makeSubtask(
"sourceSelector")
451 if self.config.kernelSourcesFromRef:
452 self.makeSubtask(
'refObjLoader', butler=butler)
453 self.makeSubtask(
"astrometer", refObjLoader=self.refObjLoader)
455 self.algMetadata = dafBase.PropertyList()
456 if self.config.doDetection:
457 self.makeSubtask(
"detection", schema=self.schema)
458 if self.config.doMeasurement:
459 self.makeSubtask(
"measurement", schema=self.schema,
460 algMetadata=self.algMetadata)
461 if self.config.doApCorr:
462 self.makeSubtask(
"applyApCorr", schema=self.measurement.schema)
463 if self.config.doForcedMeasurement:
464 self.schema.addField(
465 "ip_diffim_forced_PsfFlux_instFlux",
"D",
466 "Forced PSF flux measured on the direct image.",
468 self.schema.addField(
469 "ip_diffim_forced_PsfFlux_instFluxErr",
"D",
470 "Forced PSF flux error measured on the direct image.",
472 self.schema.addField(
473 "ip_diffim_forced_PsfFlux_area",
"F",
474 "Forced PSF flux effective area of PSF.",
476 self.schema.addField(
477 "ip_diffim_forced_PsfFlux_flag",
"Flag",
478 "Forced PSF flux general failure flag.")
479 self.schema.addField(
480 "ip_diffim_forced_PsfFlux_flag_noGoodPixels",
"Flag",
481 "Forced PSF flux not enough non-rejected pixels in data to attempt the fit.")
482 self.schema.addField(
483 "ip_diffim_forced_PsfFlux_flag_edge",
"Flag",
484 "Forced PSF flux object was too close to the edge of the image to use the full PSF model.")
485 self.makeSubtask(
"forcedMeasurement", refSchema=self.schema)
486 if self.config.doMatchSources:
487 self.schema.addField(
"refMatchId",
"L",
"unique id of reference catalog match")
488 self.schema.addField(
"srcMatchId",
"L",
"unique id of source match")
489 if self.config.doSkySources:
490 self.makeSubtask(
"skySources")
491 self.skySourceKey = self.schema.addField(
"sky_source", type=
"Flag", doc=
"Sky objects.")
494 self.outputSchema = afwTable.SourceCatalog(self.schema)
495 self.outputSchema.getTable().setMetadata(self.algMetadata)
498 def makeIdFactory(expId, expBits):
499 """Create IdFactory instance for unique 64 bit diaSource id-s.
507 Number of used bits in ``expId``.
511 The diasource id-s consists of the ``expId`` stored fixed in the highest value
512 ``expBits`` of the 64-bit integer plus (bitwise or) a generated sequence number in the
513 low value end of the integer.
517 idFactory: `lsst.afw.table.IdFactory`
519 return ExposureIdInfo(expId, expBits).makeSourceIdFactory()
521 @lsst.utils.inheritDoc(pipeBase.PipelineTask)
522 def runQuantum(self, butlerQC: pipeBase.ButlerQuantumContext,
523 inputRefs: pipeBase.InputQuantizedConnection,
524 outputRefs: pipeBase.OutputQuantizedConnection):
525 inputs = butlerQC.get(inputRefs)
526 self.log.info(
"Processing %s", butlerQC.quantum.dataId)
527 expId, expBits = butlerQC.quantum.dataId.pack(
"visit_detector",
529 idFactory = self.makeIdFactory(expId=expId, expBits=expBits)
530 if self.config.coaddName ==
'dcr':
531 templateExposures = inputRefs.dcrCoadds
533 templateExposures = inputRefs.coaddExposures
534 templateStruct = self.getTemplate.runQuantum(
535 inputs[
'exposure'], butlerQC, inputRefs.skyMap, templateExposures
538 if templateStruct.area/inputs[
'exposure'].getBBox().getArea() < self.config.requiredTemplateFraction:
539 message = (
"Insufficient Template Coverage. (%.1f%% < %.1f%%) Not attempting subtraction. "
540 "To force subtraction, set config requiredTemplateFraction=0." % (
541 100*templateStruct.area/inputs[
'exposure'].getBBox().getArea(),
542 100*self.config.requiredTemplateFraction))
543 raise pipeBase.NoWorkFound(message)
545 outputs = self.run(exposure=inputs[
'exposure'],
546 templateExposure=templateStruct.exposure,
549 if outputs.diaSources
is None:
550 del outputs.diaSources
551 butlerQC.put(outputs, outputRefs)
554 def runDataRef(self, sensorRef, templateIdList=None):
555 """Subtract an image from a template coadd and measure the result.
557 Data I/O wrapper around `run` using the butler in Gen2.
561 sensorRef : `lsst.daf.persistence.ButlerDataRef`
562 Sensor-level butler data reference, used for the following data products:
569 - self.config.coaddName + "Coadd_skyMap"
570 - self.config.coaddName + "Coadd"
571 Input or output, depending on config:
572 - self.config.coaddName + "Diff_subtractedExp"
573 Output, depending on config:
574 - self.config.coaddName + "Diff_matchedExp"
575 - self.config.coaddName + "Diff_src"
579 results : `lsst.pipe.base.Struct`
580 Returns the Struct by `run`.
582 subtractedExposureName = self.config.coaddName +
"Diff_differenceExp"
583 subtractedExposure =
None
585 calexpBackgroundExposure =
None
586 self.log.info(
"Processing %s", sensorRef.dataId)
591 idFactory = self.makeIdFactory(expId=int(sensorRef.get(
"ccdExposureId")),
592 expBits=sensorRef.get(
"ccdExposureId_bits"))
593 if self.config.doAddCalexpBackground:
594 calexpBackgroundExposure = sensorRef.get(
"calexpBackground")
597 exposure = sensorRef.get(
"calexp", immediate=
True)
600 template = self.getTemplate.runDataRef(exposure, sensorRef, templateIdList=templateIdList)
602 if sensorRef.datasetExists(
"src"):
603 self.log.info(
"Source selection via src product")
605 selectSources = sensorRef.get(
"src")
607 if not self.config.doSubtract
and self.config.doDetection:
609 subtractedExposure = sensorRef.get(subtractedExposureName)
612 results = self.run(exposure=exposure,
613 selectSources=selectSources,
614 templateExposure=template.exposure,
615 templateSources=template.sources,
617 calexpBackgroundExposure=calexpBackgroundExposure,
618 subtractedExposure=subtractedExposure)
620 if self.config.doWriteSources
and results.diaSources
is not None:
621 sensorRef.put(results.diaSources, self.config.coaddName +
"Diff_diaSrc")
622 if self.config.doWriteWarpedExp:
623 sensorRef.put(results.warpedExposure, self.config.coaddName +
"Diff_warpedExp")
624 if self.config.doWriteMatchedExp:
625 sensorRef.put(results.matchedExposure, self.config.coaddName +
"Diff_matchedExp")
626 if self.config.doAddMetrics
and self.config.doSelectSources:
627 sensorRef.put(results.selectSources, self.config.coaddName +
"Diff_kernelSrc")
628 if self.config.doWriteSubtractedExp:
629 sensorRef.put(results.subtractedExposure, subtractedExposureName)
630 if self.config.doWriteScoreExp:
631 sensorRef.put(results.scoreExposure, self.config.coaddName +
"Diff_scoreExp")
635 def run(self, exposure=None, selectSources=None, templateExposure=None, templateSources=None,
636 idFactory=None, calexpBackgroundExposure=None, subtractedExposure=None):
637 """PSF matches, subtract two images and perform detection on the difference image.
641 exposure : `lsst.afw.image.ExposureF`, optional
642 The science exposure, the minuend in the image subtraction.
643 Can be None only if ``config.doSubtract==False``.
644 selectSources : `lsst.afw.table.SourceCatalog`, optional
645 Identified sources on the science exposure. This catalog is used to
646 select sources in order to perform the AL PSF matching on stamp images
647 around them. The selection steps depend on config options and whether
648 ``templateSources`` and ``matchingSources`` specified.
649 templateExposure : `lsst.afw.image.ExposureF`, optional
650 The template to be subtracted from ``exposure`` in the image subtraction.
651 ``templateExposure`` is modified in place if ``config.doScaleTemplateVariance==True``.
652 The template exposure should cover the same sky area as the science exposure.
653 It is either a stich of patches of a coadd skymap image or a calexp
654 of the same pointing as the science exposure. Can be None only
655 if ``config.doSubtract==False`` and ``subtractedExposure`` is not None.
656 templateSources : `lsst.afw.table.SourceCatalog`, optional
657 Identified sources on the template exposure.
658 idFactory : `lsst.afw.table.IdFactory`
659 Generator object to assign ids to detected sources in the difference image.
660 calexpBackgroundExposure : `lsst.afw.image.ExposureF`, optional
661 Background exposure to be added back to the science exposure
662 if ``config.doAddCalexpBackground==True``
663 subtractedExposure : `lsst.afw.image.ExposureF`, optional
664 If ``config.doSubtract==False`` and ``config.doDetection==True``,
665 performs the post subtraction source detection only on this exposure.
666 Otherwise should be None.
670 results : `lsst.pipe.base.Struct`
671 ``subtractedExposure`` : `lsst.afw.image.ExposureF`
673 ``scoreExposure`` : `lsst.afw.image.ExposureF` or `None`
674 The zogy score exposure, if calculated.
675 ``matchedExposure`` : `lsst.afw.image.ExposureF`
676 The matched PSF exposure.
677 ``subtractRes`` : `lsst.pipe.base.Struct`
678 The returned result structure of the ImagePsfMatchTask subtask.
679 ``diaSources`` : `lsst.afw.table.SourceCatalog`
680 The catalog of detected sources.
681 ``selectSources`` : `lsst.afw.table.SourceCatalog`
682 The input source catalog with optionally added Qa information.
686 The following major steps are included:
688 - warp template coadd to match WCS of image
689 - PSF match image to warped template
690 - subtract image from PSF-matched, warped template
694 For details about the image subtraction configuration modes
695 see `lsst.ip.diffim`.
698 controlSources =
None
703 exposureOrig = exposure
705 if self.config.doAddCalexpBackground:
706 mi = exposure.getMaskedImage()
707 mi += calexpBackgroundExposure.getImage()
709 if not exposure.hasPsf():
710 raise pipeBase.TaskError(
"Exposure has no psf")
711 sciencePsf = exposure.getPsf()
713 if self.config.doSubtract:
714 if self.config.doScaleTemplateVariance:
715 self.log.info(
"Rescaling template variance")
716 templateVarFactor = self.scaleVariance.
run(
717 templateExposure.getMaskedImage())
718 self.log.info(
"Template variance scaling factor: %.2f", templateVarFactor)
719 self.metadata.add(
"scaleTemplateVarianceFactor", templateVarFactor)
721 if self.config.subtract.name ==
'zogy':
722 subtractRes = self.subtract.
run(exposure, templateExposure, doWarping=
True)
723 scoreExposure = subtractRes.scoreExp
724 subtractedExposure = subtractRes.diffExp
725 subtractRes.subtractedExposure = subtractedExposure
726 subtractRes.matchedExposure =
None
728 elif self.config.subtract.name ==
'al':
730 scienceSigmaOrig = sciencePsf.computeShape().getDeterminantRadius()
731 templateSigma = templateExposure.getPsf().computeShape().getDeterminantRadius()
739 if self.config.doPreConvolve:
740 convControl = afwMath.ConvolutionControl()
742 srcMI = exposure.maskedImage
743 exposure = exposure.clone()
745 if self.config.useGaussianForPreConvolution:
747 kWidth, kHeight = sciencePsf.getLocalKernel().getDimensions()
752 afwMath.convolve(exposure.maskedImage, srcMI, preConvPsf.getLocalKernel(), convControl)
753 scienceSigmaPost = scienceSigmaOrig*math.sqrt(2)
755 scienceSigmaPost = scienceSigmaOrig
760 if self.config.doSelectSources:
761 if selectSources
is None:
762 self.log.warning(
"Src product does not exist; running detection, measurement,"
765 selectSources = self.subtract.getSelectSources(
767 sigma=scienceSigmaPost,
768 doSmooth=
not self.config.doPreConvolve,
772 if self.config.doAddMetrics:
775 nparam = len(makeKernelBasisList(self.subtract.config.kernel.active,
776 referenceFwhmPix=scienceSigmaPost*FwhmPerSigma,
777 targetFwhmPix=templateSigma*FwhmPerSigma))
784 kcQa = KernelCandidateQa(nparam)
785 selectSources = kcQa.addToSchema(selectSources)
786 if self.config.kernelSourcesFromRef:
788 astromRet = self.astrometer.loadAndMatch(exposure=exposure, sourceCat=selectSources)
789 matches = astromRet.matches
790 elif templateSources:
792 mc = afwTable.MatchControl()
793 mc.findOnlyClosest =
False
794 matches = afwTable.matchRaDec(templateSources, selectSources, 1.0*geom.arcseconds,
797 raise RuntimeError(
"doSelectSources=True and kernelSourcesFromRef=False,"
798 "but template sources not available. Cannot match science "
799 "sources with template sources. Run process* on data from "
800 "which templates are built.")
802 kernelSources = self.sourceSelector.
run(selectSources, exposure=exposure,
803 matches=matches).sourceCat
804 random.shuffle(kernelSources, random.random)
805 controlSources = kernelSources[::self.config.controlStepSize]
806 kernelSources = [k
for i, k
in enumerate(kernelSources)
807 if i % self.config.controlStepSize]
809 if self.config.doSelectDcrCatalog:
810 redSelector = DiaCatalogSourceSelectorTask(
811 DiaCatalogSourceSelectorConfig(grMin=self.sourceSelector.config.grMax,
813 redSources = redSelector.selectStars(exposure, selectSources, matches=matches).starCat
814 controlSources.extend(redSources)
816 blueSelector = DiaCatalogSourceSelectorTask(
817 DiaCatalogSourceSelectorConfig(grMin=-99.999,
818 grMax=self.sourceSelector.config.grMin))
819 blueSources = blueSelector.selectStars(exposure, selectSources,
820 matches=matches).starCat
821 controlSources.extend(blueSources)
823 if self.config.doSelectVariableCatalog:
824 varSelector = DiaCatalogSourceSelectorTask(
825 DiaCatalogSourceSelectorConfig(includeVariable=
True))
826 varSources = varSelector.selectStars(exposure, selectSources, matches=matches).starCat
827 controlSources.extend(varSources)
829 self.log.info(
"Selected %d / %d sources for Psf matching (%d for control sample)",
830 len(kernelSources), len(selectSources), len(controlSources))
834 if self.config.doUseRegister:
835 self.log.info(
"Registering images")
837 if templateSources
is None:
841 templateSigma = templateExposure.getPsf().computeShape().getDeterminantRadius()
842 templateSources = self.subtract.getSelectSources(
851 wcsResults = self.fitAstrometry(templateSources, templateExposure, selectSources)
852 warpedExp = self.register.warpExposure(templateExposure, wcsResults.wcs,
853 exposure.getWcs(), exposure.getBBox())
854 templateExposure = warpedExp
859 if self.config.doDebugRegister:
861 srcToMatch = {x.second.getId(): x.first
for x
in matches}
863 refCoordKey = wcsResults.matches[0].first.getTable().getCoordKey()
864 inCentroidKey = wcsResults.matches[0].second.getTable().getCentroidSlot().getMeasKey()
865 sids = [m.first.getId()
for m
in wcsResults.matches]
866 positions = [m.first.get(refCoordKey)
for m
in wcsResults.matches]
867 residuals = [m.first.get(refCoordKey).getOffsetFrom(wcsResults.wcs.pixelToSky(
868 m.second.get(inCentroidKey)))
for m
in wcsResults.matches]
869 allresids = dict(zip(sids, zip(positions, residuals)))
871 cresiduals = [m.first.get(refCoordKey).getTangentPlaneOffset(
872 wcsResults.wcs.pixelToSky(
873 m.second.get(inCentroidKey)))
for m
in wcsResults.matches]
874 colors = numpy.array([-2.5*numpy.log10(srcToMatch[x].get(
"g"))
875 + 2.5*numpy.log10(srcToMatch[x].get(
"r"))
876 for x
in sids
if x
in srcToMatch.keys()])
877 dlong = numpy.array([r[0].asArcseconds()
for s, r
in zip(sids, cresiduals)
878 if s
in srcToMatch.keys()])
879 dlat = numpy.array([r[1].asArcseconds()
for s, r
in zip(sids, cresiduals)
880 if s
in srcToMatch.keys()])
881 idx1 = numpy.where(colors < self.sourceSelector.config.grMin)
882 idx2 = numpy.where((colors >= self.sourceSelector.config.grMin)
883 & (colors <= self.sourceSelector.config.grMax))
884 idx3 = numpy.where(colors > self.sourceSelector.config.grMax)
885 rms1Long = IqrToSigma*(
886 (numpy.percentile(dlong[idx1], 75) - numpy.percentile(dlong[idx1], 25)))
887 rms1Lat = IqrToSigma*(numpy.percentile(dlat[idx1], 75)
888 - numpy.percentile(dlat[idx1], 25))
889 rms2Long = IqrToSigma*(
890 (numpy.percentile(dlong[idx2], 75) - numpy.percentile(dlong[idx2], 25)))
891 rms2Lat = IqrToSigma*(numpy.percentile(dlat[idx2], 75)
892 - numpy.percentile(dlat[idx2], 25))
893 rms3Long = IqrToSigma*(
894 (numpy.percentile(dlong[idx3], 75) - numpy.percentile(dlong[idx3], 25)))
895 rms3Lat = IqrToSigma*(numpy.percentile(dlat[idx3], 75)
896 - numpy.percentile(dlat[idx3], 25))
897 self.log.info(
"Blue star offsets'': %.3f %.3f, %.3f %.3f",
898 numpy.median(dlong[idx1]), rms1Long,
899 numpy.median(dlat[idx1]), rms1Lat)
900 self.log.info(
"Green star offsets'': %.3f %.3f, %.3f %.3f",
901 numpy.median(dlong[idx2]), rms2Long,
902 numpy.median(dlat[idx2]), rms2Lat)
903 self.log.info(
"Red star offsets'': %.3f %.3f, %.3f %.3f",
904 numpy.median(dlong[idx3]), rms3Long,
905 numpy.median(dlat[idx3]), rms3Lat)
907 self.metadata.add(
"RegisterBlueLongOffsetMedian", numpy.median(dlong[idx1]))
908 self.metadata.add(
"RegisterGreenLongOffsetMedian", numpy.median(dlong[idx2]))
909 self.metadata.add(
"RegisterRedLongOffsetMedian", numpy.median(dlong[idx3]))
910 self.metadata.add(
"RegisterBlueLongOffsetStd", rms1Long)
911 self.metadata.add(
"RegisterGreenLongOffsetStd", rms2Long)
912 self.metadata.add(
"RegisterRedLongOffsetStd", rms3Long)
914 self.metadata.add(
"RegisterBlueLatOffsetMedian", numpy.median(dlat[idx1]))
915 self.metadata.add(
"RegisterGreenLatOffsetMedian", numpy.median(dlat[idx2]))
916 self.metadata.add(
"RegisterRedLatOffsetMedian", numpy.median(dlat[idx3]))
917 self.metadata.add(
"RegisterBlueLatOffsetStd", rms1Lat)
918 self.metadata.add(
"RegisterGreenLatOffsetStd", rms2Lat)
919 self.metadata.add(
"RegisterRedLatOffsetStd", rms3Lat)
926 self.log.info(
"Subtracting images")
927 subtractRes = self.subtract.subtractExposures(
928 templateExposure=templateExposure,
929 scienceExposure=exposure,
930 candidateList=kernelSources,
931 convolveTemplate=self.config.convolveTemplate,
932 doWarping=
not self.config.doUseRegister
934 subtractedExposure = subtractRes.subtractedExposure
936 if self.config.doDetection:
937 self.log.info(
"Computing diffim PSF")
940 if not subtractedExposure.hasPsf():
941 if self.config.convolveTemplate:
942 subtractedExposure.setPsf(exposure.getPsf())
944 subtractedExposure.setPsf(templateExposure.getPsf())
951 if self.config.doDecorrelation
and self.config.doSubtract:
953 if preConvPsf
is not None:
954 preConvKernel = preConvPsf.getLocalKernel()
955 decorrResult = self.decorrelate.
run(exposureOrig, subtractRes.warpedExposure,
957 subtractRes.psfMatchingKernel,
958 spatiallyVarying=self.config.doSpatiallyVarying,
959 preConvKernel=preConvKernel,
960 templateMatched=self.config.convolveTemplate)
961 subtractedExposure = decorrResult.correctedExposure
965 if self.config.doDetection:
966 self.log.info(
"Running diaSource detection")
974 if self.config.useScoreImageDetection:
976 self.log.info(
"Detection, diffim rescaling and measurements are on Zogy score image.")
977 detectionExposure = scoreExposure
978 detectOnLikelihood =
True
981 detectionExposure = subtractedExposure
982 detectOnLikelihood =
False
983 if self.config.doPreConvolve:
985 self.log.info(
"Detection, diffim rescaling and measurements are on AL pre-convolved "
986 "difference (likelihood) image.")
987 detectOnLikelihood =
True
989 self.log.info(
"Detection, diffim rescaling and measurements are on "
990 "(proper) difference image.")
993 if self.config.doScaleDiffimVariance:
994 self.log.info(
"Rescaling diffim variance")
995 diffimVarFactor = self.scaleVariance.
run(detectionExposure.getMaskedImage())
996 self.log.info(
"Diffim variance scaling factor: %.2f", diffimVarFactor)
997 self.metadata.add(
"scaleDiffimVarianceFactor", diffimVarFactor)
1000 mask = detectionExposure.getMaskedImage().getMask()
1001 mask &= ~(mask.getPlaneBitMask(
"DETECTED") | mask.getPlaneBitMask(
"DETECTED_NEGATIVE"))
1003 table = afwTable.SourceTable.make(self.schema, idFactory)
1004 table.setMetadata(self.algMetadata)
1005 results = self.detection.
run(
1007 exposure=detectionExposure,
1008 doSmooth=
not detectOnLikelihood
1011 if self.config.doMerge:
1012 fpSet = results.fpSets.positive
1013 fpSet.merge(results.fpSets.negative, self.config.growFootprint,
1014 self.config.growFootprint,
False)
1015 diaSources = afwTable.SourceCatalog(table)
1016 fpSet.makeSources(diaSources)
1017 self.log.info(
"Merging detections into %d sources", len(diaSources))
1019 diaSources = results.sources
1021 if self.config.doSkySources:
1022 skySourceFootprints = self.skySources.
run(
1023 mask=detectionExposure.mask,
1024 seed=detectionExposure.getInfo().getVisitInfo().getExposureId())
1025 if skySourceFootprints:
1026 for foot
in skySourceFootprints:
1027 s = diaSources.addNew()
1028 s.setFootprint(foot)
1029 s.set(self.skySourceKey,
True)
1031 if self.config.doMeasurement:
1032 newDipoleFitting = self.config.doDipoleFitting
1033 self.log.info(
"Running diaSource measurement: newDipoleFitting=%r", newDipoleFitting)
1034 if not newDipoleFitting:
1036 self.measurement.
run(diaSources, detectionExposure)
1039 if self.config.doSubtract
and 'matchedExposure' in subtractRes.getDict():
1040 self.measurement.
run(diaSources, detectionExposure, exposure,
1041 subtractRes.matchedExposure)
1043 self.measurement.
run(diaSources, detectionExposure, exposure)
1044 if self.config.doApCorr:
1045 self.applyApCorr.
run(
1047 apCorrMap=detectionExposure.getInfo().getApCorrMap()
1050 if self.config.doForcedMeasurement:
1053 forcedSources = self.forcedMeasurement.generateMeasCat(
1054 exposure, diaSources, detectionExposure.getWcs())
1055 self.forcedMeasurement.
run(forcedSources, exposure, diaSources, detectionExposure.getWcs())
1056 mapper = afwTable.SchemaMapper(forcedSources.schema, diaSources.schema)
1057 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_instFlux")[0],
1058 "ip_diffim_forced_PsfFlux_instFlux",
True)
1059 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_instFluxErr")[0],
1060 "ip_diffim_forced_PsfFlux_instFluxErr",
True)
1061 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_area")[0],
1062 "ip_diffim_forced_PsfFlux_area",
True)
1063 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_flag")[0],
1064 "ip_diffim_forced_PsfFlux_flag",
True)
1065 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_flag_noGoodPixels")[0],
1066 "ip_diffim_forced_PsfFlux_flag_noGoodPixels",
True)
1067 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_flag_edge")[0],
1068 "ip_diffim_forced_PsfFlux_flag_edge",
True)
1069 for diaSource, forcedSource
in zip(diaSources, forcedSources):
1070 diaSource.assign(forcedSource, mapper)
1073 if self.config.doMatchSources:
1074 if selectSources
is not None:
1076 matchRadAsec = self.config.diaSourceMatchRadius
1077 matchRadPixel = matchRadAsec/exposure.getWcs().getPixelScale().asArcseconds()
1079 srcMatches = afwTable.matchXy(selectSources, diaSources, matchRadPixel)
1080 srcMatchDict = dict([(srcMatch.second.getId(), srcMatch.first.getId())
for
1081 srcMatch
in srcMatches])
1082 self.log.info(
"Matched %d / %d diaSources to sources",
1083 len(srcMatchDict), len(diaSources))
1085 self.log.warning(
"Src product does not exist; cannot match with diaSources")
1089 refAstromConfig = AstrometryConfig()
1090 refAstromConfig.matcher.maxMatchDistArcSec = matchRadAsec
1091 refAstrometer = AstrometryTask(refAstromConfig)
1092 astromRet = refAstrometer.run(exposure=exposure, sourceCat=diaSources)
1093 refMatches = astromRet.matches
1094 if refMatches
is None:
1095 self.log.warning(
"No diaSource matches with reference catalog")
1098 self.log.info(
"Matched %d / %d diaSources to reference catalog",
1099 len(refMatches), len(diaSources))
1100 refMatchDict = dict([(refMatch.second.getId(), refMatch.first.getId())
for
1101 refMatch
in refMatches])
1104 for diaSource
in diaSources:
1105 sid = diaSource.getId()
1106 if sid
in srcMatchDict:
1107 diaSource.set(
"srcMatchId", srcMatchDict[sid])
1108 if sid
in refMatchDict:
1109 diaSource.set(
"refMatchId", refMatchDict[sid])
1111 if self.config.doAddMetrics
and self.config.doSelectSources:
1112 self.log.info(
"Evaluating metrics and control sample")
1115 for cell
in subtractRes.kernelCellSet.getCellList():
1116 for cand
in cell.begin(
False):
1117 kernelCandList.append(cand)
1120 basisList = kernelCandList[0].getKernel(KernelCandidateF.ORIG).getKernelList()
1121 nparam = len(kernelCandList[0].getKernel(KernelCandidateF.ORIG).getKernelParameters())
1124 diffimTools.sourceTableToCandidateList(controlSources,
1125 subtractRes.warpedExposure, exposure,
1126 self.config.subtract.kernel.active,
1127 self.config.subtract.kernel.active.detectionConfig,
1128 self.log, doBuild=
True, basisList=basisList))
1130 KernelCandidateQa.apply(kernelCandList, subtractRes.psfMatchingKernel,
1131 subtractRes.backgroundModel, dof=nparam)
1132 KernelCandidateQa.apply(controlCandList, subtractRes.psfMatchingKernel,
1133 subtractRes.backgroundModel)
1135 if self.config.doDetection:
1136 KernelCandidateQa.aggregate(selectSources, self.metadata, allresids, diaSources)
1138 KernelCandidateQa.aggregate(selectSources, self.metadata, allresids)
1140 self.runDebug(exposure, subtractRes, selectSources, kernelSources, diaSources)
1141 return pipeBase.Struct(
1142 subtractedExposure=subtractedExposure,
1143 scoreExposure=scoreExposure,
1144 warpedExposure=subtractRes.warpedExposure,
1145 matchedExposure=subtractRes.matchedExposure,
1146 subtractRes=subtractRes,
1147 diaSources=diaSources,
1148 selectSources=selectSources
1151 def fitAstrometry(self, templateSources, templateExposure, selectSources):
1152 """Fit the relative astrometry between templateSources and selectSources
1157 Remove this method. It originally fit a new WCS to the template before calling register.run
1158 because our TAN-SIP fitter behaved badly for points far from CRPIX, but that's been fixed.
1159 It remains because a subtask overrides it.
1161 results = self.register.
run(templateSources, templateExposure.getWcs(),
1162 templateExposure.getBBox(), selectSources)
1165 def runDebug(self, exposure, subtractRes, selectSources, kernelSources, diaSources):
1166 """Make debug plots and displays.
1170 Test and update for current debug display and slot names
1180 disp = afwDisplay.getDisplay(frame=lsstDebug.frame)
1181 if not maskTransparency:
1182 maskTransparency = 0
1183 disp.setMaskTransparency(maskTransparency)
1185 if display
and showSubtracted:
1186 disp.mtv(subtractRes.subtractedExposure, title=
"Subtracted image")
1187 mi = subtractRes.subtractedExposure.getMaskedImage()
1188 x0, y0 = mi.getX0(), mi.getY0()
1189 with disp.Buffering():
1190 for s
in diaSources:
1191 x, y = s.getX() - x0, s.getY() - y0
1192 ctype =
"red" if s.get(
"flags_negative")
else "yellow"
1193 if (s.get(
"base_PixelFlags_flag_interpolatedCenter")
1194 or s.get(
"base_PixelFlags_flag_saturatedCenter")
1195 or s.get(
"base_PixelFlags_flag_crCenter")):
1197 elif (s.get(
"base_PixelFlags_flag_interpolated")
1198 or s.get(
"base_PixelFlags_flag_saturated")
1199 or s.get(
"base_PixelFlags_flag_cr")):
1203 disp.dot(ptype, x, y, size=4, ctype=ctype)
1204 lsstDebug.frame += 1
1206 if display
and showPixelResiduals
and selectSources:
1207 nonKernelSources = []
1208 for source
in selectSources:
1209 if source
not in kernelSources:
1210 nonKernelSources.append(source)
1212 diUtils.plotPixelResiduals(exposure,
1213 subtractRes.warpedExposure,
1214 subtractRes.subtractedExposure,
1215 subtractRes.kernelCellSet,
1216 subtractRes.psfMatchingKernel,
1217 subtractRes.backgroundModel,
1219 self.subtract.config.kernel.active.detectionConfig,
1221 diUtils.plotPixelResiduals(exposure,
1222 subtractRes.warpedExposure,
1223 subtractRes.subtractedExposure,
1224 subtractRes.kernelCellSet,
1225 subtractRes.psfMatchingKernel,
1226 subtractRes.backgroundModel,
1228 self.subtract.config.kernel.active.detectionConfig,
1230 if display
and showDiaSources:
1231 flagChecker = SourceFlagChecker(diaSources)
1232 isFlagged = [flagChecker(x)
for x
in diaSources]
1233 isDipole = [x.get(
"ip_diffim_ClassificationDipole_value")
for x
in diaSources]
1234 diUtils.showDiaSources(diaSources, subtractRes.subtractedExposure, isFlagged, isDipole,
1235 frame=lsstDebug.frame)
1236 lsstDebug.frame += 1
1238 if display
and showDipoles:
1239 DipoleAnalysis().displayDipoles(subtractRes.subtractedExposure, diaSources,
1240 frame=lsstDebug.frame)
1241 lsstDebug.frame += 1
1243 def _getConfigName(self):
1244 """Return the name of the config dataset
1246 return "%sDiff_config" % (self.config.coaddName,)
1248 def _getMetadataName(self):
1249 """Return the name of the metadata dataset
1251 return "%sDiff_metadata" % (self.config.coaddName,)
1253 def getSchemaCatalogs(self):
1254 """Return a dict of empty catalogs for each catalog dataset produced by this task."""
1255 return {self.config.coaddName +
"Diff_diaSrc": self.outputSchema}
1258 def _makeArgumentParser(cls):
1259 """Create an argument parser
1261 parser = pipeBase.ArgumentParser(name=cls._DefaultName)
1262 parser.add_id_argument(
"--id",
"calexp", help=
"data ID, e.g. --id visit=12345 ccd=1,2")
1263 parser.add_id_argument(
"--templateId",
"calexp", doMakeDataRefList=
True,
1264 help=
"Template data ID in case of calexp template,"
1265 " e.g. --templateId visit=6789")
1269 class Winter2013ImageDifferenceConfig(ImageDifferenceConfig):
1270 winter2013WcsShift = pexConfig.Field(dtype=float, default=0.0,
1271 doc=
"Shift stars going into RegisterTask by this amount")
1272 winter2013WcsRms = pexConfig.Field(dtype=float, default=0.0,
1273 doc=
"Perturb stars going into RegisterTask by this amount")
1275 def setDefaults(self):
1276 ImageDifferenceConfig.setDefaults(self)
1277 self.getTemplate.retarget(GetCalexpAsTemplateTask)
1280 class Winter2013ImageDifferenceTask(ImageDifferenceTask):
1281 """!Image difference Task used in the Winter 2013 data challege.
1282 Enables testing the effects of registration shifts and scatter.
1284 For use with winter 2013 simulated images:
1285 Use --templateId visit=88868666 for sparse data
1286 --templateId visit=22222200 for dense data (g)
1287 --templateId visit=11111100 for dense data (i)
1289 ConfigClass = Winter2013ImageDifferenceConfig
1290 _DefaultName =
"winter2013ImageDifference"
1292 def __init__(self, **kwargs):
1293 ImageDifferenceTask.__init__(self, **kwargs)
1295 def fitAstrometry(self, templateSources, templateExposure, selectSources):
1296 """Fit the relative astrometry between templateSources and selectSources"""
1297 if self.config.winter2013WcsShift > 0.0:
1299 self.config.winter2013WcsShift)
1300 cKey = templateSources[0].getTable().getCentroidSlot().getMeasKey()
1301 for source
in templateSources:
1302 centroid = source.get(cKey)
1303 source.set(cKey, centroid + offset)
1304 elif self.config.winter2013WcsRms > 0.0:
1305 cKey = templateSources[0].getTable().getCentroidSlot().getMeasKey()
1306 for source
in templateSources:
1307 offset =
geom.Extent2D(self.config.winter2013WcsRms*numpy.random.normal(),
1308 self.config.winter2013WcsRms*numpy.random.normal())
1309 centroid = source.get(cKey)
1310 source.set(cKey, centroid + offset)
1312 results = self.register.
run(templateSources, templateExposure.getWcs(),
1313 templateExposure.getBBox(), selectSources)
def run(self, skyInfo, tempExpRefList, imageScalerList, weightList, altMaskList=None, mask=None, supplementaryData=None)