33 __all__ = (
"SourceDetectionConfig",
"SourceDetectionTask",
"getBackground",
34 "estimateBackground",
"BackgroundConfig",
"addExposures")
39 """!Config for background estimation
41 statisticsProperty = pexConfig.ChoiceField(
42 doc=
"type of statistic to use for grid points",
43 dtype=str, default=
"MEANCLIP",
45 "MEANCLIP":
"clipped mean",
46 "MEAN":
"unclipped mean",
50 undersampleStyle = pexConfig.ChoiceField(
51 doc=
"behaviour if there are too few points in grid for requested interpolation style",
52 dtype=str, default=
"REDUCE_INTERP_ORDER",
54 "THROW_EXCEPTION":
"throw an exception if there are too few points",
55 "REDUCE_INTERP_ORDER":
"use an interpolation style with a lower order.",
56 "INCREASE_NXNYSAMPLE":
"Increase the number of samples used to make the interpolation grid.",
59 binSize = pexConfig.RangeField(
60 doc=
"how large a region of the sky should be used for each background point",
61 dtype=int, default=256, min=10
63 algorithm = pexConfig.ChoiceField(
64 doc=
"how to interpolate the background values. This maps to an enum; see afw::math::Background",
65 dtype=str, default=
"NATURAL_SPLINE", optional=
True,
67 "CONSTANT" :
"Use a single constant value",
68 "LINEAR" :
"Use linear interpolation",
69 "NATURAL_SPLINE" :
"cubic spline with zero second derivative at endpoints",
70 "AKIMA_SPLINE":
"higher-level nonlinear spline that is more robust to outliers",
71 "NONE":
"No background estimation is to be attempted",
74 ignoredPixelMask = pexConfig.ListField(
75 doc=
"Names of mask planes to ignore while estimating the background",
76 dtype=str, default = [
"EDGE",
"DETECTED",
"DETECTED_NEGATIVE"],
77 itemCheck =
lambda x: x
in afwImage.MaskU().getMaskPlaneDict().keys(),
79 isNanSafe = pexConfig.Field(
80 doc=
"Ignore NaNs when estimating the background",
81 dtype=bool, default=
False,
85 pexConfig.Config.validate(self)
91 """!Configuration parameters for the SourceDetectionTask
93 minPixels = pexConfig.RangeField(
94 doc=
"detected sources with fewer than the specified number of pixels will be ignored",
95 dtype=int, optional=
False, default=1, min=0,
97 isotropicGrow = pexConfig.Field(
98 doc=
"Pixels should be grown as isotropically as possible (slower)",
99 dtype=bool, optional=
False, default=
False,
101 nSigmaToGrow = pexConfig.Field(
102 doc=
"Grow detections by nSigmaToGrow * sigma; if 0 then do not grow",
103 dtype=float, default=2.4,
105 returnOriginalFootprints = pexConfig.Field(
106 doc=
"Grow detections to set the image mask bits, but return the original (not-grown) footprints",
107 dtype=bool, optional=
False, default=
True
109 thresholdValue = pexConfig.RangeField(
110 doc=
"Threshold for footprints",
111 dtype=float, optional=
False, default=5.0, min=0.0,
113 includeThresholdMultiplier = pexConfig.RangeField(
114 doc=
"Include threshold relative to thresholdValue",
115 dtype=float, default=1.0, min=0.0,
117 thresholdType = pexConfig.ChoiceField(
118 doc=
"specifies the desired flavor of Threshold",
119 dtype=str, optional=
False, default=
"stdev",
121 "variance":
"threshold applied to image variance",
122 "stdev":
"threshold applied to image std deviation",
123 "value":
"threshold applied to image value",
124 "pixel_stdev":
"threshold applied to per-pixel std deviation",
127 thresholdPolarity = pexConfig.ChoiceField(
128 doc=
"specifies whether to detect positive, or negative sources, or both",
129 dtype=str, optional=
False, default=
"positive",
131 "positive":
"detect only positive sources",
132 "negative":
"detect only negative sources",
133 "both":
"detect both positive and negative sources",
136 adjustBackground = pexConfig.Field(
138 doc =
"Fiddle factor to add to the background; debugging only",
141 reEstimateBackground = pexConfig.Field(
143 doc =
"Estimate the background again after final source detection?",
144 default =
True, optional=
False,
146 background = pexConfig.ConfigField(
147 dtype=BackgroundConfig,
148 doc=
"Background re-estimation configuration"
160 \anchor SourceDetectionTask_
162 \brief Detect positive and negative sources on an exposure and return a new \link table.SourceCatalog\endlink.
164 \section meas_algorithms_detection_Contents Contents
166 - \ref meas_algorithms_detection_Purpose
167 - \ref meas_algorithms_detection_Initialize
168 - \ref meas_algorithms_detection_Invoke
169 - \ref meas_algorithms_detection_Config
170 - \ref meas_algorithms_detection_Debug
171 - \ref meas_algorithms_detection_Example
173 \section meas_algorithms_detection_Purpose Description
175 \copybrief SourceDetectionTask
177 \section meas_algorithms_detection_Initialize Task initialisation
181 \section meas_algorithms_detection_Invoke Invoking the Task
185 \section meas_algorithms_detection_Config Configuration parameters
187 See \ref SourceDetectionConfig
189 \section meas_algorithms_detection_Debug Debug variables
191 The \link lsst.pipe.base.cmdLineTask.CmdLineTask command line task\endlink interface supports a
192 flag \c -d to import \b debug.py from your \c PYTHONPATH; see \ref baseDebug for more about \b debug.py files.
194 The available variables in SourceDetectionTask are:
198 - If True, display the exposure on ds9's frame 0. +ve detections in blue, -ve detections in cyan
199 - If display > 1, display the convolved exposure on frame 1
202 \section meas_algorithms_detection_Example A complete example of using SourceDetectionTask
204 This code is in \link measAlgTasks.py\endlink in the examples directory, and can be run as \em e.g.
206 examples/measAlgTasks.py --ds9
208 \dontinclude measAlgTasks.py
209 The example also runs the SourceMeasurementTask; see \ref meas_algorithms_measurement_Example for more explanation.
211 Import the task (there are some other standard imports; read the file if you're confused)
212 \skipline SourceDetectionTask
214 We need to create our task before processing any data as the task constructor
215 can add an extra column to the schema, but first we need an almost-empty Schema
216 \skipline makeMinimalSchema
217 after which we can call the constructor:
218 \skip SourceDetectionTask.ConfigClass
221 We're now ready to process the data (we could loop over multiple exposures/catalogues using the same
222 task objects). First create the output table:
225 And process the image
227 (You may not be happy that the threshold was set in the config before creating the Task rather than being set
228 separately for each exposure. You \em can reset it just before calling the run method if you must, but we
229 should really implement a better solution).
231 We can then unpack and use the results:
236 To investigate the \ref meas_algorithms_detection_Debug, put something like
240 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
241 if name == "lsst.meas.algorithms.detection":
246 lsstDebug.Info = DebugInfo
248 into your debug.py file and run measAlgTasks.py with the \c --debug flag.
250 ConfigClass = SourceDetectionConfig
251 _DefaultName =
"sourceDetection"
254 def init(self, schema=None, **kwds):
255 """!Create the detection task. Most arguments are simply passed onto pipe.base.Task.
257 \param schema An lsst::afw::table::Schema used to create the output lsst.afw.table.SourceCatalog
258 \param **kwds Keyword arguments passed to lsst.pipe.base.task.Task.__init__.
260 If schema is not None, a 'flags.negative' field will be added to label detections
261 made with a negative threshold.
263 \note This task can add fields to the schema, so any code calling this task must ensure that
264 these columns are indeed present in the input match list; see \ref Example
269 """!Create the detection task. See SourceDetectionTask.init for documentation
271 pipeBase.Task.__init__(self, **kwds)
272 if schema
is not None:
274 "flags_negative", type=
"Flag",
275 doc=
"set if source was detected as significantly negative"
278 if self.config.thresholdPolarity ==
"both":
279 self.log.log(self.log.WARN,
"Detection polarity set to 'both', but no flag will be "\
280 "set to distinguish between positive and negative detections")
284 def run(self, table, exposure, doSmooth=True, sigma=None, clearMask=True):
285 """!Run source detection and create a SourceCatalog.
287 \param table lsst.afw.table.SourceTable object that will be used to create the SourceCatalog.
288 \param exposure Exposure to process; DETECTED mask plane will be set in-place.
289 \param doSmooth if True, smooth the image before detection using a Gaussian of width sigma (default: True)
290 \param sigma sigma of PSF (pixels); used for smoothing and to grow detections;
291 if None then measure the sigma of the PSF of the exposure (default: None)
292 \param clearMask Clear DETECTED{,_NEGATIVE} planes before running detection (default: True)
294 \return a lsst.pipe.base.Struct with:
295 - sources -- an lsst.afw.table.SourceCatalog object
296 - fpSets --- lsst.pipe.base.Struct returned by \link detectFootprints \endlink
298 \throws ValueError if flags.negative is needed, but isn't in table's schema
299 \throws lsst.pipe.base.TaskError if sigma=None, doSmooth=True and the exposure has no PSF
302 If you want to avoid dealing with Sources and Tables, you can use detectFootprints()
303 to just get the afw::detection::FootprintSet%s.
307 raise ValueError(
"Table has incorrect Schema")
308 fpSets = self.
detectFootprints(exposure=exposure, doSmooth=doSmooth, sigma=sigma,
311 table.preallocate(fpSets.numPos + fpSets.numNeg)
313 fpSets.negative.makeSources(sources)
315 for record
in sources:
318 fpSets.positive.makeSources(sources)
319 return pipeBase.Struct(
325 makeSourceCatalog = run
329 """!Detect footprints.
331 \param exposure Exposure to process; DETECTED{,_NEGATIVE} mask plane will be set in-place.
332 \param doSmooth if True, smooth the image before detection using a Gaussian of width sigma
333 \param sigma sigma of PSF (pixels); used for smoothing and to grow detections;
334 if None then measure the sigma of the PSF of the exposure
335 \param clearMask Clear both DETECTED and DETECTED_NEGATIVE planes before running detection
337 \return a lsst.pipe.base.Struct with fields:
338 - positive: lsst.afw.detection.FootprintSet with positive polarity footprints (may be None)
339 - negative: lsst.afw.detection.FootprintSet with negative polarity footprints (may be None)
340 - numPos: number of footprints in positive or 0 if detection polarity was negative
341 - numNeg: number of footprints in negative or 0 if detection polarity was positive
342 - background: re-estimated background. None if reEstimateBackground==False
344 \throws lsst.pipe.base.TaskError if sigma=None and the exposure has no PSF
356 raise RuntimeError(
"No exposure for detection")
358 maskedImage = exposure.getMaskedImage()
359 region = maskedImage.getBBox()
362 mask = maskedImage.getMask()
363 mask &= ~(mask.getPlaneBitMask(
"DETECTED") | mask.getPlaneBitMask(
"DETECTED_NEGATIVE"))
367 psf = exposure.getPsf()
369 raise pipeBase.TaskError(
"exposure has no PSF; must specify sigma")
370 shape = psf.computeShape()
371 sigma = shape.getDeterminantRadius()
373 self.metadata.set(
"sigma", sigma)
374 self.metadata.set(
"doSmooth", doSmooth)
377 convolvedImage = maskedImage.Factory(maskedImage)
378 middle = convolvedImage
382 psf = exposure.getPsf()
383 kWidth = (int(sigma * 7 + 0.5) // 2) * 2 + 1
384 self.metadata.set(
"smoothingKernelWidth", kWidth)
385 gaussFunc = afwMath.GaussianFunction1D(sigma)
388 convolvedImage = maskedImage.Factory(maskedImage.getBBox())
394 goodBBox = gaussKernel.shrinkBBox(convolvedImage.getBBox())
395 middle = convolvedImage.Factory(convolvedImage, goodBBox, afwImage.PARENT,
False)
399 self.
setEdgeBits(maskedImage, goodBBox, maskedImage.getMask().getPlaneBitMask(
"EDGE"))
401 fpSets = pipeBase.Struct(positive=
None, negative=
None)
403 if self.config.thresholdPolarity !=
"negative":
405 if self.config.reEstimateBackground
or self.config.thresholdPolarity !=
"positive":
408 for polarity, maskName
in ((
"positive",
"DETECTED"), (
"negative",
"DETECTED_NEGATIVE")):
409 fpSet = getattr(fpSets, polarity)
412 fpSet.setRegion(region)
413 if self.config.nSigmaToGrow > 0:
414 nGrow = int((self.config.nSigmaToGrow * sigma) + 0.5)
415 self.metadata.set(
"nGrow", nGrow)
417 fpSet.setMask(maskedImage.getMask(), maskName)
418 if not self.config.returnOriginalFootprints:
419 setattr(fpSets, polarity, fpSet)
421 fpSets.numPos = len(fpSets.positive.getFootprints())
if fpSets.positive
is not None else 0
422 fpSets.numNeg = len(fpSets.negative.getFootprints())
if fpSets.negative
is not None else 0
424 if self.config.thresholdPolarity !=
"negative":
425 self.log.log(self.log.INFO,
"Detected %d positive sources to %g sigma." %
426 (fpSets.numPos, self.config.thresholdValue))
428 fpSets.background =
None
429 if self.config.reEstimateBackground:
430 mi = exposure.getMaskedImage()
433 if self.config.adjustBackground:
434 self.log.log(self.log.WARN,
"Fiddling the background by %g" % self.config.adjustBackground)
436 bkgd += self.config.adjustBackground
437 fpSets.background = bkgd
438 self.log.log(self.log.INFO,
"Resubtracting the background after object detection")
439 mi -= bkgd.getImageF()
442 if self.config.thresholdPolarity ==
"positive":
443 if self.config.reEstimateBackground:
444 mask = maskedImage.getMask()
445 mask &= ~mask.getPlaneBitMask(
"DETECTED_NEGATIVE")
447 fpSets.negative =
None
449 self.log.log(self.log.INFO,
"Detected %d negative sources to %g %s" %
450 (fpSets.numNeg, self.config.thresholdValue,
451 (
"DN" if self.config.thresholdType ==
"value" else "sigma")))
454 ds9.mtv(exposure, frame=0, title=
"detection")
456 if convolvedImage
and display
and display > 1:
457 ds9.mtv(convolvedImage, frame=1, title=
"PSF smoothed")
462 """!Threshold the convolved image, returning a FootprintSet.
463 Helper function for detect().
465 \param image The (optionally convolved) MaskedImage to threshold
466 \param thresholdParity Parity of threshold
467 \param maskName Name of mask to set
471 parity =
False if thresholdParity ==
"negative" else True
473 threshold.setIncludeMultiplier(self.config.includeThresholdMultiplier)
479 """!Set the edgeBitmask bits for all of maskedImage outside goodBBox
481 \param[in,out] maskedImage image on which to set edge bits in the mask
482 \param[in] goodBBox bounding box of good pixels, in LOCAL coordinates
483 \param[in] edgeBitmask bit mask to OR with the existing mask bits in the region outside goodBBox
485 msk = maskedImage.getMask()
487 mx0, my0 = maskedImage.getXY0()
488 for x0, y0, w, h
in ([0, 0,
489 msk.getWidth(), goodBBox.getBeginY() - my0],
490 [0, goodBBox.getEndY() - my0, msk.getWidth(),
491 maskedImage.getHeight() - (goodBBox.getEndY() - my0)],
493 goodBBox.getBeginX() - mx0, msk.getHeight()],
494 [goodBBox.getEndX() - mx0, 0,
495 maskedImage.getWidth() - (goodBBox.getEndX() - mx0), msk.getHeight()],
499 edgeMask |= edgeBitmask
502 """!Add a set of exposures together.
504 \param[in] exposureList sequence of exposures to add
506 \return an exposure of the same size as each exposure in exposureList,
507 with the metadata from exposureList[0] and a masked image equal to the
508 sum of all the exposure's masked images.
510 \throw LsstException if the exposures do not all have the same dimensions (but does not check xy0)
512 exposure0 = exposureList[0]
513 image0 = exposure0.getMaskedImage()
515 addedImage = image0.Factory(image0,
True)
516 addedImage.setXY0(image0.getXY0())
518 for exposure
in exposureList[1:]:
519 image = exposure.getMaskedImage()
522 addedExposure = exposure0.Factory(addedImage, exposure0.getWcs())
526 """!Estimate the background of an image (a thin layer on lsst.afw.math.makeBackground)
528 \param[in] image image whose background is to be computed
529 \param[in] backgroundConfig configuration (a BackgroundConfig)
530 \param[in] nx number of x bands; 0 for default
531 \param[in] ny number of y bands; 0 for default
532 \param[in] algorithm name of interpolation algorithm; see lsst.afw.math.BackgroundControl for details
534 backgroundConfig.validate();
537 nx = image.getWidth()//backgroundConfig.binSize + 1
539 ny = image.getHeight()//backgroundConfig.binSize + 1
542 sctrl.setAndMask(reduce(
lambda x, y: x | image.getMask().getPlaneBitMask(y),
543 backgroundConfig.ignoredPixelMask, 0x0))
544 sctrl.setNanSafe(backgroundConfig.isNanSafe)
547 pl.debug(3,
"Ignoring mask planes: %s" %
", ".join(backgroundConfig.ignoredPixelMask))
550 algorithm = backgroundConfig.algorithm
553 backgroundConfig.undersampleStyle, sctrl,
554 backgroundConfig.statisticsProperty)
558 getBackground.ConfigClass = BackgroundConfig
562 """!Estimate exposure's background using parameters in backgroundConfig.
564 If subtract is true, make a copy of the exposure and subtract the background.
565 If `stats` is True, measure the mean and variance of the background and
566 add them to the background-subtracted exposure's metadata with keys
567 "BGMEAN" and "BGVAR", or the keys given in `statsKeys` (2-tuple of strings).
569 Return background, backgroundSubtractedExposure
573 maskedImage = exposure.getMaskedImage()
578 raise RuntimeError,
"Unable to estimate background for exposure"
582 if displayBackground > 1:
583 bgimg = background.getImageF()
584 ds9.mtv(bgimg, title=
"background", frame=1)
587 return background,
None
589 bbox = maskedImage.getBBox()
590 backgroundSubtractedExposure = exposure.Factory(exposure, bbox, afwImage.PARENT,
True)
591 copyImage = backgroundSubtractedExposure.getMaskedImage().getImage()
593 bgimg = background.getImageF()
599 if statsKeys
is None:
603 mnkey,varkey = statsKeys
604 meta = backgroundSubtractedExposure.getMetadata()
606 bgmean = s.getValue(afwMath.MEAN)
607 bgvar = s.getValue(afwMath.VARIANCE)
608 meta.addDouble(mnkey, bgmean)
609 meta.addDouble(varkey, bgvar)
611 if displayBackground:
612 ds9.mtv(backgroundSubtractedExposure, title=
"subtracted")
614 return background, backgroundSubtractedExposure
615 estimateBackground.ConfigClass = BackgroundConfig
boost::shared_ptr< Background > makeBackground(ImageT const &img, BackgroundControl const &bgCtrl)
A convenience function that uses function overloading to make the correct type of Background...
def detectFootprints
Detect footprints.
def addExposures
Add a set of exposures together.
Parameters to control convolution.
Config for background estimation.
A kernel described by a pair of functions: func(x, y) = colFunc(x) * rowFunc(y)
def setEdgeBits
Set the edgeBitmask bits for all of maskedImage outside goodBBox.
Detect positive and negative sources on an exposure and return a new table.SourceCatalog.
Pass parameters to a Background object.
An integer coordinate rectangle.
Pass parameters to a Statistics objectA class to pass parameters which control how the stats are calc...
def run
Run source detection and create a SourceCatalog.
Configuration parameters for the SourceDetectionTask.
Custom catalog class for record/table subclasses that are guaranteed to have an ID, and should generally be sorted by that ID.
def init
Create the detection task.
def __init__
Create the detection task.
def estimateBackground
Estimate exposure's background using parameters in backgroundConfig.
Threshold createThreshold(const double value, const std::string type="value", const bool polarity=true)
Factory method for creating Threshold objects.
def thresholdImage
Threshold the convolved image, returning a FootprintSet.
Statistics makeStatistics(afwImage::Mask< afwImage::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl)
Specialization to handle Masks.
void convolve(OutImageT &convolvedImage, InImageT const &inImage, KernelT const &kernel, bool doNormalize, bool doCopyEdge=false)
Old, deprecated version of convolve.
def getBackground
Estimate the background of an image (a thin layer on lsst.afw.math.makeBackground) ...