30 from .subtractBackground
import SubtractBackgroundTask
32 __all__ = (
"SourceDetectionConfig",
"SourceDetectionTask",
"addExposures")
36 """!Configuration parameters for the SourceDetectionTask
38 minPixels = pexConfig.RangeField(
39 doc=
"detected sources with fewer than the specified number of pixels will be ignored",
40 dtype=int, optional=
False, default=1, min=0,
42 isotropicGrow = pexConfig.Field(
43 doc=
"Pixels should be grown as isotropically as possible (slower)",
44 dtype=bool, optional=
False, default=
False,
46 nSigmaToGrow = pexConfig.Field(
47 doc=
"Grow detections by nSigmaToGrow * sigma; if 0 then do not grow",
48 dtype=float, default=2.4,
50 returnOriginalFootprints = pexConfig.Field(
51 doc=
"Grow detections to set the image mask bits, but return the original (not-grown) footprints",
52 dtype=bool, optional=
False, default=
False,
54 thresholdValue = pexConfig.RangeField(
55 doc=
"Threshold for footprints",
56 dtype=float, optional=
False, default=5.0, min=0.0,
58 includeThresholdMultiplier = pexConfig.RangeField(
59 doc=
"Include threshold relative to thresholdValue",
60 dtype=float, default=1.0, min=0.0,
62 thresholdType = pexConfig.ChoiceField(
63 doc=
"specifies the desired flavor of Threshold",
64 dtype=str, optional=
False, default=
"stdev",
66 "variance":
"threshold applied to image variance",
67 "stdev":
"threshold applied to image std deviation",
68 "value":
"threshold applied to image value",
69 "pixel_stdev":
"threshold applied to per-pixel std deviation",
72 thresholdPolarity = pexConfig.ChoiceField(
73 doc=
"specifies whether to detect positive, or negative sources, or both",
74 dtype=str, optional=
False, default=
"positive",
76 "positive":
"detect only positive sources",
77 "negative":
"detect only negative sources",
78 "both":
"detect both positive and negative sources",
81 adjustBackground = pexConfig.Field(
83 doc=
"Fiddle factor to add to the background; debugging only",
86 reEstimateBackground = pexConfig.Field(
88 doc=
"Estimate the background again after final source detection?",
89 default=
True, optional=
False,
91 background = pexConfig.ConfigurableField(
92 doc=
"Background re-estimation; ignored if reEstimateBackground false",
93 target=SubtractBackgroundTask,
95 tempLocalBackground = pexConfig.ConfigurableField(
96 doc=(
"A seperate background estimation and removal before footprint and peak detection. "
97 "It is added back into the image after detection."),
98 target=SubtractBackgroundTask,
100 doTempLocalBackground = pexConfig.Field(
102 doc=
"Do temporary interpolated background subtraction before footprint detection?",
107 self.tempLocalBackground.binSize = 64
108 self.tempLocalBackground.algorithm =
"AKIMA_SPLINE"
109 self.tempLocalBackground.useApprox =
False
121 \anchor SourceDetectionTask_
123 \brief Detect positive and negative sources on an exposure and return a new \link table.SourceCatalog\endlink.
125 \section meas_algorithms_detection_Contents Contents
127 - \ref meas_algorithms_detection_Purpose
128 - \ref meas_algorithms_detection_Initialize
129 - \ref meas_algorithms_detection_Invoke
130 - \ref meas_algorithms_detection_Config
131 - \ref meas_algorithms_detection_Debug
132 - \ref meas_algorithms_detection_Example
134 \section meas_algorithms_detection_Purpose Description
136 \copybrief SourceDetectionTask
138 \section meas_algorithms_detection_Initialize Task initialisation
140 \copydoc \_\_init\_\_
142 \section meas_algorithms_detection_Invoke Invoking the Task
146 \section meas_algorithms_detection_Config Configuration parameters
148 See \ref SourceDetectionConfig
150 \section meas_algorithms_detection_Debug Debug variables
152 The \link lsst.pipe.base.cmdLineTask.CmdLineTask command line task\endlink interface supports a
153 flag \c -d to import \b debug.py from your \c PYTHONPATH; see \ref baseDebug for more about \b debug.py files.
155 The available variables in SourceDetectionTask are:
159 - If True, display the exposure on ds9's frame 0. +ve detections in blue, -ve detections in cyan
160 - If display > 1, display the convolved exposure on frame 1
163 \section meas_algorithms_detection_Example A complete example of using SourceDetectionTask
165 This code is in \link measAlgTasks.py\endlink in the examples directory, and can be run as \em e.g.
167 examples/measAlgTasks.py --ds9
169 \dontinclude measAlgTasks.py
170 The example also runs the SourceMeasurementTask; see \ref meas_algorithms_measurement_Example for more
173 Import the task (there are some other standard imports; read the file if you're confused)
174 \skipline SourceDetectionTask
176 We need to create our task before processing any data as the task constructor
177 can add an extra column to the schema, but first we need an almost-empty Schema
178 \skipline makeMinimalSchema
179 after which we can call the constructor:
180 \skip SourceDetectionTask.ConfigClass
183 We're now ready to process the data (we could loop over multiple exposures/catalogues using the same
184 task objects). First create the output table:
187 And process the image
189 (You may not be happy that the threshold was set in the config before creating the Task rather than being set
190 separately for each exposure. You \em can reset it just before calling the run method if you must, but we
191 should really implement a better solution).
193 We can then unpack and use the results:
198 To investigate the \ref meas_algorithms_detection_Debug, put something like
202 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
203 if name == "lsst.meas.algorithms.detection":
208 lsstDebug.Info = DebugInfo
210 into your debug.py file and run measAlgTasks.py with the \c --debug flag.
212 ConfigClass = SourceDetectionConfig
213 _DefaultName =
"sourceDetection"
216 """!Create the detection task. Most arguments are simply passed onto pipe.base.Task.
218 \param schema An lsst::afw::table::Schema used to create the output lsst.afw.table.SourceCatalog
219 \param **kwds Keyword arguments passed to lsst.pipe.base.task.Task.__init__.
221 If schema is not None, a 'flags.negative' field will be added to label detections
222 made with a negative threshold.
224 \note This task can add fields to the schema, so any code calling this task must ensure that
225 these columns are indeed present in the input match list; see \ref Example
227 pipeBase.Task.__init__(self, **kwds)
228 if schema
is not None:
230 "flags_negative", type=
"Flag",
231 doc=
"set if source was detected as significantly negative"
234 if self.config.thresholdPolarity ==
"both":
235 self.log.log(self.log.WARN,
"Detection polarity set to 'both', but no flag will be "
236 "set to distinguish between positive and negative detections")
238 if self.config.reEstimateBackground:
239 self.makeSubtask(
"background")
240 if self.config.doTempLocalBackground:
241 self.makeSubtask(
"tempLocalBackground")
244 def run(self, table, exposure, doSmooth=True, sigma=None, clearMask=True):
245 """!Run source detection and create a SourceCatalog.
247 \param table lsst.afw.table.SourceTable object that will be used to create the SourceCatalog.
248 \param exposure Exposure to process; DETECTED mask plane will be set in-place.
249 \param doSmooth if True, smooth the image before detection using a Gaussian of width sigma
251 \param sigma sigma of PSF (pixels); used for smoothing and to grow detections;
252 if None then measure the sigma of the PSF of the exposure (default: None)
253 \param clearMask Clear DETECTED{,_NEGATIVE} planes before running detection (default: True)
255 \return a lsst.pipe.base.Struct with:
256 - sources -- an lsst.afw.table.SourceCatalog object
257 - fpSets --- lsst.pipe.base.Struct returned by \link detectFootprints \endlink
259 \throws ValueError if flags.negative is needed, but isn't in table's schema
260 \throws lsst.pipe.base.TaskError if sigma=None, doSmooth=True and the exposure has no PSF
263 If you want to avoid dealing with Sources and Tables, you can use detectFootprints()
264 to just get the afw::detection::FootprintSet%s.
267 raise ValueError(
"Table has incorrect Schema")
268 fpSets = self.
detectFootprints(exposure=exposure, doSmooth=doSmooth, sigma=sigma,
271 table.preallocate(fpSets.numPos + fpSets.numNeg)
273 fpSets.negative.makeSources(sources)
275 for record
in sources:
278 fpSets.positive.makeSources(sources)
279 return pipeBase.Struct(
285 makeSourceCatalog = run
289 """!Detect footprints.
291 \param exposure Exposure to process; DETECTED{,_NEGATIVE} mask plane will be set in-place.
292 \param doSmooth if True, smooth the image before detection using a Gaussian of width sigma
293 \param sigma sigma of PSF (pixels); used for smoothing and to grow detections;
294 if None then measure the sigma of the PSF of the exposure
295 \param clearMask Clear both DETECTED and DETECTED_NEGATIVE planes before running detection
297 \return a lsst.pipe.base.Struct with fields:
298 - positive: lsst.afw.detection.FootprintSet with positive polarity footprints (may be None)
299 - negative: lsst.afw.detection.FootprintSet with negative polarity footprints (may be None)
300 - numPos: number of footprints in positive or 0 if detection polarity was negative
301 - numNeg: number of footprints in negative or 0 if detection polarity was positive
302 - background: re-estimated background. None if reEstimateBackground==False
304 \throws lsst.pipe.base.TaskError if sigma=None and the exposure has no PSF
316 raise RuntimeError(
"No exposure for detection")
318 maskedImage = exposure.getMaskedImage()
319 region = maskedImage.getBBox()
322 mask = maskedImage.getMask()
323 mask &= ~(mask.getPlaneBitMask(
"DETECTED") | mask.getPlaneBitMask(
"DETECTED_NEGATIVE"))
326 if self.config.doTempLocalBackground:
327 tempBgRes = self.tempLocalBackground.run(exposure)
328 tempLocalBkgdImage = tempBgRes.background.getImage()
331 psf = exposure.getPsf()
333 raise pipeBase.TaskError(
"exposure has no PSF; must specify sigma")
334 shape = psf.computeShape()
335 sigma = shape.getDeterminantRadius()
337 self.metadata.set(
"sigma", sigma)
338 self.metadata.set(
"doSmooth", doSmooth)
341 convolvedImage = maskedImage.Factory(maskedImage)
342 middle = convolvedImage
346 psf = exposure.getPsf()
347 kWidth = (int(sigma * 7 + 0.5) // 2) * 2 + 1
348 self.metadata.set(
"smoothingKernelWidth", kWidth)
349 gaussFunc = afwMath.GaussianFunction1D(sigma)
352 convolvedImage = maskedImage.Factory(maskedImage.getBBox())
358 goodBBox = gaussKernel.shrinkBBox(convolvedImage.getBBox())
359 middle = convolvedImage.Factory(convolvedImage, goodBBox, afwImage.PARENT,
False)
363 self.
setEdgeBits(maskedImage, goodBBox, maskedImage.getMask().getPlaneBitMask(
"EDGE"))
365 fpSets = pipeBase.Struct(positive=
None, negative=
None)
367 if self.config.thresholdPolarity !=
"negative":
369 if self.config.reEstimateBackground
or self.config.thresholdPolarity !=
"positive":
372 for polarity, maskName
in ((
"positive",
"DETECTED"), (
"negative",
"DETECTED_NEGATIVE")):
373 fpSet = getattr(fpSets, polarity)
376 fpSet.setRegion(region)
377 if self.config.nSigmaToGrow > 0:
378 nGrow = int((self.config.nSigmaToGrow * sigma) + 0.5)
379 self.metadata.set(
"nGrow", nGrow)
381 fpSet.setMask(maskedImage.getMask(), maskName)
382 if not self.config.returnOriginalFootprints:
383 setattr(fpSets, polarity, fpSet)
385 fpSets.numPos = len(fpSets.positive.getFootprints())
if fpSets.positive
is not None else 0
386 fpSets.numNeg = len(fpSets.negative.getFootprints())
if fpSets.negative
is not None else 0
388 if self.config.thresholdPolarity !=
"negative":
389 self.log.info(
"Detected %d positive sources to %g sigma.",
390 fpSets.numPos, self.config.thresholdValue*self.config.includeThresholdMultiplier)
392 if self.config.doTempLocalBackground:
393 maskedImage += tempLocalBkgdImage
395 fpSets.background =
None
396 if self.config.reEstimateBackground:
397 mi = exposure.getMaskedImage()
398 bkgd = self.background.fitBackground(mi)
400 if self.config.adjustBackground:
401 self.log.warn(
"Fiddling the background by %g", self.config.adjustBackground)
403 bkgd += self.config.adjustBackground
404 fpSets.background = bkgd
405 self.log.info(
"Resubtracting the background after object detection")
407 mi -= bkgd.getImageF()
410 if self.config.thresholdPolarity ==
"positive":
411 if self.config.reEstimateBackground:
412 mask = maskedImage.getMask()
413 mask &= ~mask.getPlaneBitMask(
"DETECTED_NEGATIVE")
415 fpSets.negative =
None
417 self.log.info(
"Detected %d negative sources to %g %s",
418 fpSets.numNeg, self.config.thresholdValue,
419 (
"DN" if self.config.thresholdType ==
"value" else "sigma"))
422 ds9.mtv(exposure, frame=0, title=
"detection")
423 x0, y0 = exposure.getXY0()
425 def plotPeaks(fps, ctype):
428 with ds9.Buffering():
429 for fp
in fps.getFootprints():
430 for pp
in fp.getPeaks():
431 ds9.dot(
"+", pp.getFx() - x0, pp.getFy() - y0, ctype=ctype)
432 plotPeaks(fpSets.positive,
"yellow")
433 plotPeaks(fpSets.negative,
"red")
435 if convolvedImage
and display
and display > 1:
436 ds9.mtv(convolvedImage, frame=1, title=
"PSF smoothed")
441 """!Threshold the convolved image, returning a FootprintSet.
442 Helper function for detect().
444 \param image The (optionally convolved) MaskedImage to threshold
445 \param thresholdParity Parity of threshold
446 \param maskName Name of mask to set
450 parity =
False if thresholdParity ==
"negative" else True
452 threshold.setIncludeMultiplier(self.config.includeThresholdMultiplier)
454 if self.config.thresholdType ==
'stdev':
455 bad = image.getMask().getPlaneBitMask([
'BAD',
'SAT',
'EDGE',
'NO_DATA', ])
457 sctrl.setAndMask(bad)
459 thres = stats.getValue(afwMath.STDEVCLIP) * self.config.thresholdValue
461 threshold.setIncludeMultiplier(self.config.includeThresholdMultiplier)
468 """!Set the edgeBitmask bits for all of maskedImage outside goodBBox
470 \param[in,out] maskedImage image on which to set edge bits in the mask
471 \param[in] goodBBox bounding box of good pixels, in LOCAL coordinates
472 \param[in] edgeBitmask bit mask to OR with the existing mask bits in the region outside goodBBox
474 msk = maskedImage.getMask()
476 mx0, my0 = maskedImage.getXY0()
477 for x0, y0, w, h
in ([0, 0,
478 msk.getWidth(), goodBBox.getBeginY() - my0],
479 [0, goodBBox.getEndY() - my0, msk.getWidth(),
480 maskedImage.getHeight() - (goodBBox.getEndY() - my0)],
482 goodBBox.getBeginX() - mx0, msk.getHeight()],
483 [goodBBox.getEndX() - mx0, 0,
484 maskedImage.getWidth() - (goodBBox.getEndX() - mx0), msk.getHeight()],
488 edgeMask |= edgeBitmask
492 """!Add a set of exposures together.
494 \param[in] exposureList sequence of exposures to add
496 \return an exposure of the same size as each exposure in exposureList,
497 with the metadata from exposureList[0] and a masked image equal to the
498 sum of all the exposure's masked images.
500 \throw LsstException if the exposures do not all have the same dimensions (but does not check xy0)
502 exposure0 = exposureList[0]
503 image0 = exposure0.getMaskedImage()
505 addedImage = image0.Factory(image0,
True)
506 addedImage.setXY0(image0.getXY0())
508 for exposure
in exposureList[1:]:
509 image = exposure.getMaskedImage()
512 addedExposure = exposure0.Factory(addedImage, exposure0.getWcs())
def detectFootprints
Detect footprints.
def addExposures
Add a set of exposures together.
Parameters to control convolution.
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.
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.
Threshold createThreshold(double const value, std::string const typeStr, bool const polarity)
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.