28from lsst.ip.diffim.utils
import evaluateMaskFraction
29from lsst.meas.algorithms import SkyObjectsTask, SourceDetectionTask, SetPrimaryFlagsTask, MaskStreaksTask
30from lsst.meas.base import ForcedMeasurementTask, ApplyApCorrTask, DetectorVisitIdGeneratorConfig
33import lsst.meas.extensions.shapeHSM
38from lsst.utils.timer
import timeMethod
40from .
import DipoleFitTask
42__all__ = [
"DetectAndMeasureConfig",
"DetectAndMeasureTask",
43 "DetectAndMeasureScoreConfig",
"DetectAndMeasureScoreTask"]
47 dimensions=(
"instrument",
"visit",
"detector"),
48 defaultTemplates={
"coaddName":
"deep",
51 science = pipeBase.connectionTypes.Input(
52 doc=
"Input science exposure.",
53 dimensions=(
"instrument",
"visit",
"detector"),
54 storageClass=
"ExposureF",
55 name=
"{fakesType}calexp"
57 matchedTemplate = pipeBase.connectionTypes.Input(
58 doc=
"Warped and PSF-matched template used to create the difference image.",
59 dimensions=(
"instrument",
"visit",
"detector"),
60 storageClass=
"ExposureF",
61 name=
"{fakesType}{coaddName}Diff_matchedExp",
63 difference = pipeBase.connectionTypes.Input(
64 doc=
"Result of subtracting template from science.",
65 dimensions=(
"instrument",
"visit",
"detector"),
66 storageClass=
"ExposureF",
67 name=
"{fakesType}{coaddName}Diff_differenceTempExp",
69 outputSchema = pipeBase.connectionTypes.InitOutput(
70 doc=
"Schema (as an example catalog) for output DIASource catalog.",
71 storageClass=
"SourceCatalog",
72 name=
"{fakesType}{coaddName}Diff_diaSrc_schema",
74 diaSources = pipeBase.connectionTypes.Output(
75 doc=
"Detected diaSources on the difference image.",
76 dimensions=(
"instrument",
"visit",
"detector"),
77 storageClass=
"SourceCatalog",
78 name=
"{fakesType}{coaddName}Diff_diaSrc",
80 subtractedMeasuredExposure = pipeBase.connectionTypes.Output(
81 doc=
"Difference image with detection mask plane filled in.",
82 dimensions=(
"instrument",
"visit",
"detector"),
83 storageClass=
"ExposureF",
84 name=
"{fakesType}{coaddName}Diff_differenceExp",
86 maskedStreaks = pipeBase.connectionTypes.Output(
87 doc=
'Streak profile information.',
88 storageClass=
"ArrowNumpyDict",
89 dimensions=(
"instrument",
"visit",
"detector"),
90 name=
"{fakesType}{coaddName}Diff_streaks",
93 def __init__(self, *, config):
94 super().__init__(config=config)
95 if not (self.config.writeStreakInfo
and self.config.doMaskStreaks):
96 self.outputs.remove(
"maskedStreaks")
99class DetectAndMeasureConfig(pipeBase.PipelineTaskConfig,
100 pipelineConnections=DetectAndMeasureConnections):
101 """Config for DetectAndMeasureTask
103 doMerge = pexConfig.Field(
106 doc=
"Merge positive and negative diaSources with grow radius "
107 "set by growFootprint"
109 doForcedMeasurement = pexConfig.Field(
112 doc=
"Force photometer diaSource locations on PVI?")
113 doAddMetrics = pexConfig.Field(
116 doc=
"Add columns to the source table to hold analysis metrics?"
118 detection = pexConfig.ConfigurableField(
119 target=SourceDetectionTask,
120 doc=
"Final source detection for diaSource measurement",
122 deblend = pexConfig.ConfigurableField(
124 doc=
"Task to split blended sources into their components."
126 measurement = pexConfig.ConfigurableField(
127 target=DipoleFitTask,
128 doc=
"Task to measure sources on the difference image.",
133 doc=
"Run subtask to apply aperture corrections"
136 target=ApplyApCorrTask,
137 doc=
"Task to apply aperture corrections"
139 forcedMeasurement = pexConfig.ConfigurableField(
140 target=ForcedMeasurementTask,
141 doc=
"Task to force photometer science image at diaSource locations.",
143 growFootprint = pexConfig.Field(
146 doc=
"Grow positive and negative footprints by this many pixels before merging"
148 diaSourceMatchRadius = pexConfig.Field(
151 doc=
"Match radius (in arcseconds) for DiaSource to Source association"
153 doSkySources = pexConfig.Field(
156 doc=
"Generate sky sources?",
158 skySources = pexConfig.ConfigurableField(
159 target=SkyObjectsTask,
160 doc=
"Generate sky sources",
162 doMaskStreaks = pexConfig.Field(
165 doc=
"Turn on streak masking",
167 maskStreaks = pexConfig.ConfigurableField(
168 target=MaskStreaksTask,
169 doc=
"Subtask for masking streaks. Only used if doMaskStreaks is True. "
170 "Adds a mask plane to an exposure, with the mask plane name set by streakMaskName.",
172 writeStreakInfo = pexConfig.Field(
175 doc=
"Record the parameters of any detected streaks. For LSST, this should be turned off except for "
178 setPrimaryFlags = pexConfig.ConfigurableField(
179 target=SetPrimaryFlagsTask,
180 doc=
"Task to add isPrimary and deblending-related flags to the catalog."
184 doc=
"Sources with any of these flags set are removed before writing the output catalog.",
185 default=(
"base_PixelFlags_flag_offimage",
186 "base_PixelFlags_flag_interpolatedCenterAll",
187 "base_PixelFlags_flag_badCenterAll",
188 "base_PixelFlags_flag_edgeCenterAll",
189 "base_PixelFlags_flag_saturatedCenterAll",
192 idGenerator = DetectorVisitIdGeneratorConfig.make_field()
194 def setDefaults(self):
196 self.detection.thresholdPolarity =
"both"
197 self.detection.thresholdValue = 5.0
198 self.detection.reEstimateBackground =
False
199 self.detection.thresholdType =
"pixel_stdev"
200 self.detection.excludeMaskPlanes = [
"EDGE"]
203 self.measurement.algorithms.names.add(
"base_PeakLikelihoodFlux")
204 self.measurement.plugins.names |= [
"ext_trailedSources_Naive",
205 "base_LocalPhotoCalib",
207 "ext_shapeHSM_HsmSourceMoments",
208 "ext_shapeHSM_HsmPsfMoments",
210 self.measurement.slots.psfShape =
"ext_shapeHSM_HsmPsfMoments"
211 self.measurement.slots.shape =
"ext_shapeHSM_HsmSourceMoments"
212 self.measurement.plugins[
"base_SdssCentroid"].maxDistToPeak = 5.0
213 self.forcedMeasurement.plugins = [
"base_TransformedCentroid",
"base_PsfFlux"]
214 self.forcedMeasurement.copyColumns = {
215 "id":
"objectId",
"parent":
"parentObjectId",
"coord_ra":
"coord_ra",
"coord_dec":
"coord_dec"}
216 self.forcedMeasurement.slots.centroid =
"base_TransformedCentroid"
217 self.forcedMeasurement.slots.shape =
None
220 self.measurement.plugins[
"base_PixelFlags"].masksFpAnywhere = [
221 "STREAK",
"INJECTED",
"INJECTED_TEMPLATE"]
222 self.measurement.plugins[
"base_PixelFlags"].masksFpCenter = [
223 "STREAK",
"INJECTED",
"INJECTED_TEMPLATE"]
224 self.skySources.avoidMask = [
"DETECTED",
"DETECTED_NEGATIVE",
"BAD",
"NO_DATA",
"EDGE"]
227class DetectAndMeasureTask(lsst.pipe.base.PipelineTask):
228 """Detect and measure sources on a difference image.
230 ConfigClass = DetectAndMeasureConfig
231 _DefaultName =
"detectAndMeasure"
233 def __init__(self, **kwargs):
234 super().__init__(**kwargs)
235 self.schema = afwTable.SourceTable.makeMinimalSchema()
237 afwTable.CoordKey.addErrorFields(self.schema)
240 self.makeSubtask(
"detection", schema=self.schema)
241 self.makeSubtask(
"deblend", schema=self.schema)
242 self.makeSubtask(
"setPrimaryFlags", schema=self.schema, isSingleFrame=
True)
243 self.makeSubtask(
"measurement", schema=self.schema,
244 algMetadata=self.algMetadata)
245 if self.config.doApCorr:
246 self.makeSubtask(
"applyApCorr", schema=self.measurement.schema)
247 if self.config.doForcedMeasurement:
248 self.schema.addField(
249 "ip_diffim_forced_PsfFlux_instFlux",
"D",
250 "Forced PSF flux measured on the direct image.",
252 self.schema.addField(
253 "ip_diffim_forced_PsfFlux_instFluxErr",
"D",
254 "Forced PSF flux error measured on the direct image.",
256 self.schema.addField(
257 "ip_diffim_forced_PsfFlux_area",
"F",
258 "Forced PSF flux effective area of PSF.",
260 self.schema.addField(
261 "ip_diffim_forced_PsfFlux_flag",
"Flag",
262 "Forced PSF flux general failure flag.")
263 self.schema.addField(
264 "ip_diffim_forced_PsfFlux_flag_noGoodPixels",
"Flag",
265 "Forced PSF flux not enough non-rejected pixels in data to attempt the fit.")
266 self.schema.addField(
267 "ip_diffim_forced_PsfFlux_flag_edge",
"Flag",
268 "Forced PSF flux object was too close to the edge of the image to use the full PSF model.")
269 self.makeSubtask(
"forcedMeasurement", refSchema=self.schema)
271 self.schema.addField(
"refMatchId",
"L",
"unique id of reference catalog match")
272 self.schema.addField(
"srcMatchId",
"L",
"unique id of source match")
273 if self.config.doSkySources:
274 self.makeSubtask(
"skySources", schema=self.schema)
275 if self.config.doMaskStreaks:
276 self.makeSubtask(
"maskStreaks")
279 for flag
in self.config.badSourceFlags:
280 if flag
not in self.schema:
281 raise pipeBase.InvalidQuantumError(
"Field %s not in schema" % flag)
285 self.outputSchema.getTable().setMetadata(self.algMetadata)
287 def runQuantum(self, butlerQC: pipeBase.QuantumContext,
288 inputRefs: pipeBase.InputQuantizedConnection,
289 outputRefs: pipeBase.OutputQuantizedConnection):
290 inputs = butlerQC.get(inputRefs)
291 idGenerator = self.config.idGenerator.apply(butlerQC.quantum.dataId)
292 idFactory = idGenerator.make_table_id_factory()
293 outputs = self.run(**inputs, idFactory=idFactory)
294 butlerQC.put(outputs, outputRefs)
297 def run(self, science, matchedTemplate, difference,
299 """Detect and measure sources on a difference image.
301 The difference image will be convolved with a gaussian approximation of
302 the PSF to form a maximum likelihood image for detection.
303 Close positive and negative detections will optionally be merged into
305 Sky sources, or forced detections in background regions, will optionally
306 be added, and the configured measurement algorithm will be run on all
311 science : `lsst.afw.image.ExposureF`
312 Science exposure that the template was subtracted from.
313 matchedTemplate : `lsst.afw.image.ExposureF`
314 Warped and PSF-matched template that was used produce the
316 difference : `lsst.afw.image.ExposureF`
317 Result of subtracting template from the science image.
318 idFactory : `lsst.afw.table.IdFactory`, optional
319 Generator object used to assign ids to detected sources in the
320 difference image. Ids from this generator are not set until after
321 deblending and merging positive/negative peaks.
325 measurementResults : `lsst.pipe.base.Struct`
327 ``subtractedMeasuredExposure`` : `lsst.afw.image.ExposureF`
328 Subtracted exposure with detection mask applied.
329 ``diaSources`` : `lsst.afw.table.SourceCatalog`
330 The catalog of detected sources.
332 if idFactory
is None:
336 mask = difference.mask
337 mask &= ~(mask.getPlaneBitMask(
"DETECTED") | mask.getPlaneBitMask(
"DETECTED_NEGATIVE"))
342 table = afwTable.SourceTable.make(self.schema)
343 results = self.detection.run(
349 sources, positives, negatives = self._deblend(difference,
353 return self.processResults(science, matchedTemplate, difference, sources, idFactory,
354 positiveFootprints=positives,
355 negativeFootprints=negatives)
357 def processResults(self, science, matchedTemplate, difference, sources, idFactory,
358 positiveFootprints=None, negativeFootprints=None,):
359 """Measure and process the results of source detection.
363 science : `lsst.afw.image.ExposureF`
364 Science exposure that the template was subtracted from.
365 matchedTemplate : `lsst.afw.image.ExposureF`
366 Warped and PSF-matched template that was used produce the
368 difference : `lsst.afw.image.ExposureF`
369 Result of subtracting template from the science image.
370 sources : `lsst.afw.table.SourceCatalog`
371 Detected sources on the difference exposure.
372 idFactory : `lsst.afw.table.IdFactory`
373 Generator object used to assign ids to detected sources in the
375 positiveFootprints : `lsst.afw.detection.FootprintSet`, optional
376 Positive polarity footprints.
377 negativeFootprints : `lsst.afw.detection.FootprintSet`, optional
378 Negative polarity footprints.
382 measurementResults : `lsst.pipe.base.Struct`
384 ``subtractedMeasuredExposure`` : `lsst.afw.image.ExposureF`
385 Subtracted exposure with detection mask applied.
386 ``diaSources`` : `lsst.afw.table.SourceCatalog`
387 The catalog of detected sources.
389 self.metadata.add(
"nUnmergedDiaSources", len(sources))
390 if self.config.doMerge:
391 fpSet = positiveFootprints
392 fpSet.merge(negativeFootprints, self.config.growFootprint,
393 self.config.growFootprint,
False)
395 fpSet.makeSources(initialDiaSources)
396 self.log.info(
"Merging detections into %d sources", len(initialDiaSources))
398 initialDiaSources = sources
402 for source
in initialDiaSources:
405 initialDiaSources.getTable().setIdFactory(idFactory)
406 initialDiaSources.setMetadata(self.algMetadata)
408 self.metadata.add(
"nMergedDiaSources", len(initialDiaSources))
410 if self.config.doMaskStreaks:
411 streakInfo = self._runStreakMasking(difference.maskedImage)
413 if self.config.doSkySources:
414 self.addSkySources(initialDiaSources, difference.mask, difference.info.id)
416 if not initialDiaSources.isContiguous():
417 initialDiaSources = initialDiaSources.copy(deep=
True)
419 self.measureDiaSources(initialDiaSources, science, difference, matchedTemplate)
420 diaSources = self._removeBadSources(initialDiaSources)
422 if self.config.doForcedMeasurement:
423 self.measureForcedSources(diaSources, science, difference.getWcs())
425 self.calculateMetrics(difference)
427 measurementResults = pipeBase.Struct(
428 subtractedMeasuredExposure=difference,
429 diaSources=diaSources,
431 if self.config.doMaskStreaks
and self.config.writeStreakInfo:
432 measurementResults.mergeItems(streakInfo,
'maskedStreaks')
434 return measurementResults
436 def _deblend(self, difference, positiveFootprints, negativeFootprints):
437 """Deblend the positive and negative footprints and return a catalog
438 containing just the children, and the deblended footprints.
442 difference : `lsst.afw.image.Exposure`
443 Result of subtracting template from the science image.
444 positiveFootprints, negativeFootprints : `lsst.afw.detection.FootprintSet`
445 Positive and negative polarity footprints measured on
446 ``difference`` to be deblended separately.
450 sources : `lsst.afw.table.SourceCatalog`
451 Positive and negative deblended children.
452 positives, negatives : `lsst.afw.detection.FootprintSet`
453 Deblended positive and negative polarity footprints measured on
456 def makeFootprints(sources):
457 footprints = afwDetection.FootprintSet(difference.getBBox())
458 footprints.setFootprints([src.getFootprint()
for src
in sources])
462 """Deblend a positive or negative footprint set,
463 and return the deblended children.
466 footprints.makeSources(sources)
467 self.deblend.run(exposure=difference, sources=sources)
468 self.setPrimaryFlags.run(sources)
469 children = sources[
"detect_isDeblendedSource"] == 1
470 sources = sources[children].copy(deep=
True)
472 sources[
'parent'] = 0
473 return sources.copy(deep=
True)
475 positives =
deblend(positiveFootprints)
476 negatives =
deblend(negativeFootprints)
479 sources.reserve(len(positives) + len(negatives))
480 sources.extend(positives, deep=
True)
481 sources.extend(negatives, deep=
True)
482 return sources, makeFootprints(positives), makeFootprints(negatives)
484 def _removeBadSources(self, diaSources):
485 """Remove unphysical diaSources from the catalog.
489 diaSources : `lsst.afw.table.SourceCatalog`
490 The catalog of detected sources.
494 diaSources : `lsst.afw.table.SourceCatalog`
495 The updated catalog of detected sources, with any source that has a
496 flag in ``config.badSourceFlags`` set removed.
498 selector = np.ones(len(diaSources), dtype=bool)
499 for flag
in self.config.badSourceFlags:
500 flags = diaSources[flag]
501 nBad = np.count_nonzero(flags)
503 self.log.debug(
"Found %d unphysical sources with flag %s.", nBad, flag)
505 nBadTotal = np.count_nonzero(~selector)
506 self.metadata.add(
"nRemovedBadFlaggedSources", nBadTotal)
507 self.log.info(
"Removed %d unphysical sources.", nBadTotal)
508 return diaSources[selector].copy(deep=
True)
510 def addSkySources(self, diaSources, mask, seed,
512 """Add sources in empty regions of the difference image
513 for measuring the background.
517 diaSources : `lsst.afw.table.SourceCatalog`
518 The catalog of detected sources.
519 mask : `lsst.afw.image.Mask`
520 Mask plane for determining regions where Sky sources can be added.
522 Seed value to initialize the random number generator.
525 subtask = self.skySources
526 skySourceFootprints = subtask.run(mask=mask, seed=seed, catalog=diaSources)
527 self.metadata.add(f
"n_{subtask.getName()}", len(skySourceFootprints))
529 def measureDiaSources(self, diaSources, science, difference, matchedTemplate):
530 """Use (matched) template and science image to constrain dipole fitting.
534 diaSources : `lsst.afw.table.SourceCatalog`
535 The catalog of detected sources.
536 science : `lsst.afw.image.ExposureF`
537 Science exposure that the template was subtracted from.
538 difference : `lsst.afw.image.ExposureF`
539 Result of subtracting template from the science image.
540 matchedTemplate : `lsst.afw.image.ExposureF`
541 Warped and PSF-matched template that was used produce the
545 for mp
in self.config.measurement.plugins[
"base_PixelFlags"].masksFpAnywhere:
546 difference.mask.addMaskPlane(mp)
549 self.measurement.run(diaSources, difference, science, matchedTemplate)
550 if self.config.doApCorr:
551 apCorrMap = difference.getInfo().getApCorrMap()
552 if apCorrMap
is None:
553 self.log.warning(
"Difference image does not have valid aperture correction; skipping.")
555 self.applyApCorr.run(
560 def measureForcedSources(self, diaSources, science, wcs):
561 """Perform forced measurement of the diaSources on the science image.
565 diaSources : `lsst.afw.table.SourceCatalog`
566 The catalog of detected sources.
567 science : `lsst.afw.image.ExposureF`
568 Science exposure that the template was subtracted from.
569 wcs : `lsst.afw.geom.SkyWcs`
570 Coordinate system definition (wcs) for the exposure.
574 forcedSources = self.forcedMeasurement.generateMeasCat(science, diaSources, wcs)
575 self.forcedMeasurement.run(forcedSources, science, diaSources, wcs)
577 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_instFlux")[0],
578 "ip_diffim_forced_PsfFlux_instFlux",
True)
579 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_instFluxErr")[0],
580 "ip_diffim_forced_PsfFlux_instFluxErr",
True)
581 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_area")[0],
582 "ip_diffim_forced_PsfFlux_area",
True)
583 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_flag")[0],
584 "ip_diffim_forced_PsfFlux_flag",
True)
585 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_flag_noGoodPixels")[0],
586 "ip_diffim_forced_PsfFlux_flag_noGoodPixels",
True)
587 mapper.addMapping(forcedSources.schema.find(
"base_PsfFlux_flag_edge")[0],
588 "ip_diffim_forced_PsfFlux_flag_edge",
True)
589 for diaSource, forcedSource
in zip(diaSources, forcedSources):
590 diaSource.assign(forcedSource, mapper)
592 def calculateMetrics(self, difference):
593 """Add image QA metrics to the Task metadata.
597 difference : `lsst.afw.image.Exposure`
598 The target image to calculate metrics for.
601 mask = difference.mask
602 badPix = (mask.array & mask.getPlaneBitMask(self.config.detection.excludeMaskPlanes)) > 0
603 self.metadata.add(
"nGoodPixels", np.sum(~badPix))
604 self.metadata.add(
"nBadPixels", np.sum(badPix))
605 detPosPix = (mask.array & mask.getPlaneBitMask(
"DETECTED")) > 0
606 detNegPix = (mask.array & mask.getPlaneBitMask(
"DETECTED_NEGATIVE")) > 0
607 self.metadata.add(
"nPixelsDetectedPositive", np.sum(detPosPix))
608 self.metadata.add(
"nPixelsDetectedNegative", np.sum(detNegPix))
611 self.metadata.add(
"nBadPixelsDetectedPositive", np.sum(detPosPix))
612 self.metadata.add(
"nBadPixelsDetectedNegative", np.sum(detNegPix))
614 metricsMaskPlanes = list(mask.getMaskPlaneDict().keys())
615 for maskPlane
in metricsMaskPlanes:
617 self.metadata.add(
"%s_mask_fraction"%maskPlane.lower(), evaluateMaskFraction(mask, maskPlane))
618 except InvalidParameterError:
619 self.metadata.add(
"%s_mask_fraction"%maskPlane.lower(), -1)
620 self.log.info(
"Unable to calculate metrics for mask plane %s: not in image"%maskPlane)
622 def _runStreakMasking(self, maskedImage):
623 """Do streak masking at put results into catalog.
627 maskedImage: `lsst.afw.image.maskedImage`
628 The image in which to search for streaks. Must have a detection
633 streakInfo: `lsst.pipe.base.Struct`
634 ``rho`` : `np.ndarray`
635 Angle of detected streak.
636 ``theta`` : `np.ndarray`
637 Distance from center of detected streak.
638 ``sigma`` : `np.ndarray`
639 Width of streak profile.
641 streaks = self.maskStreaks.run(maskedImage)
642 if self.config.writeStreakInfo:
643 rhos = np.array([line.rho
for line
in streaks.lines])
644 thetas = np.array([line.theta
for line
in streaks.lines])
645 sigmas = np.array([line.sigma
for line
in streaks.lines])
646 streakInfo = {
'rho': rhos,
'theta': thetas,
'sigma': sigmas}
648 streakInfo = {
'rho': np.array([]),
'theta': np.array([]),
'sigma': np.array([])}
649 return pipeBase.Struct(maskedStreaks=streakInfo)
653 scoreExposure = pipeBase.connectionTypes.Input(
654 doc=
"Maximum likelihood image for detection.",
655 dimensions=(
"instrument",
"visit",
"detector"),
656 storageClass=
"ExposureF",
657 name=
"{fakesType}{coaddName}Diff_scoreExp",
661class DetectAndMeasureScoreConfig(DetectAndMeasureConfig,
662 pipelineConnections=DetectAndMeasureScoreConnections):
666class DetectAndMeasureScoreTask(DetectAndMeasureTask):
667 """Detect DIA sources using a score image,
668 and measure the detections on the difference image.
670 Source detection is run on the supplied score, or maximum likelihood,
671 image. Note that no additional convolution will be done in this case.
672 Close positive and negative detections will optionally be merged into
674 Sky sources, or forced detections in background regions, will optionally
675 be added, and the configured measurement algorithm will be run on all
678 ConfigClass = DetectAndMeasureScoreConfig
679 _DefaultName =
"detectAndMeasureScore"
682 def run(self, science, matchedTemplate, difference, scoreExposure,
684 """Detect and measure sources on a score image.
688 science : `lsst.afw.image.ExposureF`
689 Science exposure that the template was subtracted from.
690 matchedTemplate : `lsst.afw.image.ExposureF`
691 Warped and PSF-matched template that was used produce the
693 difference : `lsst.afw.image.ExposureF`
694 Result of subtracting template from the science image.
695 scoreExposure : `lsst.afw.image.ExposureF`
696 Score or maximum likelihood difference image
697 idFactory : `lsst.afw.table.IdFactory`, optional
698 Generator object used to assign ids to detected sources in the
699 difference image. Ids from this generator are not set until after
700 deblending and merging positive/negative peaks.
704 measurementResults : `lsst.pipe.base.Struct`
706 ``subtractedMeasuredExposure`` : `lsst.afw.image.ExposureF`
707 Subtracted exposure with detection mask applied.
708 ``diaSources`` : `lsst.afw.table.SourceCatalog`
709 The catalog of detected sources.
711 if idFactory
is None:
715 mask = scoreExposure.mask
716 mask &= ~(mask.getPlaneBitMask(
"DETECTED") | mask.getPlaneBitMask(
"DETECTED_NEGATIVE"))
721 table = afwTable.SourceTable.make(self.schema)
722 results = self.detection.run(
724 exposure=scoreExposure,
728 difference.mask.assign(scoreExposure.mask, scoreExposure.getBBox())
730 sources, positives, negatives = self._deblend(difference,
734 return self.processResults(science, matchedTemplate, difference, sources, idFactory,
735 positiveFootprints=positives, negativeFootprints=negatives)
A mapping between the keys of two Schemas, used to copy data between them.
Class for storing ordered metadata with comments.
run(self, coaddExposures, bbox, wcs, dataIds, physical_filter=None, **kwargs)