24 "FocalPlaneBackground",
25 "FocalPlaneBackgroundConfig",
28 "SkyMeasurementConfig",
37from scipy.ndimage
import gaussian_filter
47from lsst.pex.config import Config, Field, ListField, ChoiceField, ConfigField, RangeField, ConfigurableField
52 """Measure a robust mean of an array
56 array : `numpy.ndarray`
57 Array for which to measure the mean.
59 k-sigma rejection threshold.
64 Robust mean of `array`.
66 q1, median, q3 = numpy.percentile(array, [25.0, 50.0, 100.0])
67 good = numpy.abs(array - median) < rej*0.74*(q3 - q1)
68 return array[good].mean()
72 """Configuration for background measurement"""
73 statistic = ChoiceField(dtype=str, default=
"MEANCLIP", doc=
"type of statistic to use for grid points",
74 allowed={
"MEANCLIP":
"clipped mean",
75 "MEAN":
"unclipped mean",
77 xBinSize = RangeField(dtype=int, default=32, min=1, doc=
"Superpixel size in x")
78 yBinSize = RangeField(dtype=int, default=32, min=1, doc=
"Superpixel size in y")
79 algorithm = ChoiceField(dtype=str, default=
"NATURAL_SPLINE", optional=
True,
80 doc=
"How to interpolate the background values. "
81 "This maps to an enum; see afw::math::Background",
83 "CONSTANT":
"Use a single constant value",
84 "LINEAR":
"Use linear interpolation",
85 "NATURAL_SPLINE":
"cubic spline with zero second derivative at endpoints",
86 "AKIMA_SPLINE":
"higher-level nonlinear spline that is more robust"
88 "NONE":
"No background estimation is to be attempted",
90 mask = ListField(dtype=str, default=[
"SAT",
"BAD",
"EDGE",
"DETECTED",
"DETECTED_NEGATIVE",
"NO_DATA"],
91 doc=
"Names of mask planes to ignore while estimating the background")
95 """Parameters controlling the measurement of sky statistics"""
96 statistic = ChoiceField(dtype=str, default=
"MEANCLIP", doc=
"type of statistic to use for grid points",
97 allowed={
"MEANCLIP":
"clipped mean",
98 "MEAN":
"unclipped mean",
100 clip = Field(doc=
"Clipping threshold for background", dtype=float, default=3.0)
101 nIter = Field(doc=
"Clipping iterations for background", dtype=int, default=3)
102 mask = ListField(doc=
"Mask planes to reject", dtype=str,
103 default=[
"SAT",
"DETECTED",
"DETECTED_NEGATIVE",
"BAD",
"NO_DATA"])
107 """Configuration for SkyMeasurementTask"""
108 skyIter = Field(dtype=int, default=3, doc=
"k-sigma rejection iterations for sky scale")
109 skyRej = Field(dtype=float, default=3.0, doc=
"k-sigma rejection threshold for sky scale")
110 background = ConfigField(dtype=BackgroundConfig, doc=
"Background measurement")
111 xNumSamples = Field(dtype=int, default=4, doc=
"Number of samples in x for scaling sky frame")
112 yNumSamples = Field(dtype=int, default=4, doc=
"Number of samples in y for scaling sky frame")
113 stats = ConfigField(dtype=SkyStatsConfig, doc=
"Measurement of sky statistics in the samples")
117 """Task for creating, persisting and using sky frames
119 A sky frame is like a fringe frame (the sum of many exposures of the night sky,
120 combined
with rejection to remove astrophysical objects)
except the structure
121 is on larger scales,
and hence we bin the images
and represent them
as a
123 the dominant response of the camera to the sky background.
125 ConfigClass = SkyMeasurementConfig
128 """Retrieve sky frame from the butler
132 butler : `lsst.daf.persistence.Butler`
135 Data identifier for calib
139 sky : `lsst.afw.math.BackgroundList`
142 exp = butler.get("sky", calibId)
147 """Convert an exposure to background model
149 Calibs need to be persisted as an Exposure, so we need to convert
150 the persisted Exposure to a background model.
155 Background model
in Exposure format.
159 bg : `lsst.afw.math.BackgroundList`
162 header = bgExp.getMetadata()
163 xMin = header.getScalar("BOX.MINX")
164 yMin = header.getScalar(
"BOX.MINY")
165 xMax = header.getScalar(
"BOX.MAXX")
166 yMax = header.getScalar(
"BOX.MAXY")
167 algorithm = header.getScalar(
"ALGORITHM")
169 return afwMath.BackgroundList(
170 (afwMath.BackgroundMI(bbox, bgExp.getMaskedImage()),
171 afwMath.stringToInterpStyle(algorithm),
172 afwMath.stringToUndersampleStyle(
"REDUCE_INTERP_ORDER"),
173 afwMath.ApproximateControl.UNKNOWN,
177 """Convert a background model to an exposure
179 Calibs need to be persisted as an Exposure, so we need to convert
180 the background model to an Exposure.
184 statsImage : `lsst.afw.image.MaskedImageF`
185 Background model
's statistics image.
187 Bounding box for image.
192 Background model
in Exposure format.
194 exp = afwImage.makeExposure(statsImage)
195 header = exp.getMetadata()
196 header.set("BOX.MINX", bbox.getMinX())
197 header.set(
"BOX.MINY", bbox.getMinY())
198 header.set(
"BOX.MAXX", bbox.getMaxX())
199 header.set(
"BOX.MAXY", bbox.getMaxY())
200 header.set(
"ALGORITHM", self.config.background.algorithm)
204 """Measure a background model for image
206 This doesn't use a full-featured background model (e.g., no Chebyshev
207 approximation) because we just want the binning behaviour. This will
208 allow us to average the bins later (`averageBackgrounds`).
210 The `BackgroundMI` is wrapped
in a `BackgroundList` so it can be
211 pickled
and persisted.
216 Image
for which to measure background.
220 bgModel : `lsst.afw.math.BackgroundList`
223 stats = afwMath.StatisticsControl()
224 stats.setAndMask(image.getMask().getPlaneBitMask(self.config.background.mask))
225 stats.setNanSafe(True)
226 ctrl = afwMath.BackgroundControl(
227 self.config.background.algorithm,
228 max(int(image.getWidth()/self.config.background.xBinSize + 0.5), 1),
229 max(int(image.getHeight()/self.config.background.yBinSize + 0.5), 1),
230 "REDUCE_INTERP_ORDER",
232 self.config.background.statistic
235 bg = afwMath.makeBackground(image, ctrl)
237 return afwMath.BackgroundList((
239 afwMath.stringToInterpStyle(self.config.background.algorithm),
240 afwMath.stringToUndersampleStyle(
"REDUCE_INTERP_ORDER"),
241 afwMath.ApproximateControl.UNKNOWN,
246 """Average multiple background models
248 The input background models should be a `BackgroundList` consisting
249 of a single `BackgroundMI`.
253 bgList : `list` of `lsst.afw.math.BackgroundList`
254 Background models to average.
259 Background model in Exposure format.
261 assert all(len(bg) == 1
for bg
in bgList),
"Mixed bgList: %s" % ([len(bg)
for bg
in bgList],)
262 images = [bg[0][0].getStatsImage()
for bg
in bgList]
263 boxes = [bg[0][0].getImageBBox()
for bg
in bgList]
264 assert len(set((box.getMinX(), box.getMinY(), box.getMaxX(), box.getMaxY())
for box
in boxes)) == 1, \
265 "Bounding boxes not all equal"
269 maskVal = afwImage.Mask.getPlaneBitMask(
"BAD")
271 bad = numpy.isnan(img.getImage().getArray())
272 img.getMask().getArray()[bad] = maskVal
274 stats = afwMath.StatisticsControl()
275 stats.setAndMask(maskVal)
276 stats.setNanSafe(
True)
277 combined = afwMath.statisticsStack(images, afwMath.MEANCLIP, stats)
283 array = combined.getImage().getArray()
284 bad = numpy.isnan(array)
285 median = numpy.median(array[~bad])
292 """Measure scale of background model in image
294 We treat the sky frame much as we would a fringe frame
295 (
except the length scale of the variations
is different):
296 we measure samples on the input image
and the sky frame,
297 which we will use to determine the scaling factor
in the
298 'solveScales` method.
303 Science image
for which to measure scale.
304 skyBackground : `lsst.afw.math.BackgroundList`
305 Sky background model.
309 imageSamples : `numpy.ndarray`
310 Sample measurements on image.
311 skySamples : `numpy.ndarray`
312 Sample measurements on sky frame.
314 if isinstance(image, afwImage.Exposure):
315 image = image.getMaskedImage()
317 xNumSamples = min(self.config.xNumSamples, image.getWidth())
318 yNumSamples = min(self.config.yNumSamples, image.getHeight())
319 xLimits = numpy.linspace(0, image.getWidth(), xNumSamples + 1, dtype=int)
320 yLimits = numpy.linspace(0, image.getHeight(), yNumSamples + 1, dtype=int)
321 sky = skyBackground.getImage()
322 maskVal = image.getMask().getPlaneBitMask(self.config.stats.mask)
323 ctrl = afwMath.StatisticsControl(self.config.stats.clip, self.config.stats.nIter, maskVal)
324 statistic = afwMath.stringToStatisticsProperty(self.config.stats.statistic)
327 for xIndex, yIndex
in itertools.product(range(xNumSamples), range(yNumSamples)):
329 xStart, xStop = xLimits[xIndex], xLimits[xIndex + 1] - 1
330 yStart, yStop = yLimits[yIndex], yLimits[yIndex + 1] - 1
332 subImage = image.Factory(image, box)
333 subSky = sky.Factory(sky, box)
334 imageSamples.append(afwMath.makeStatistics(subImage, statistic, ctrl).getValue())
335 skySamples.append(afwMath.makeStatistics(subSky, statistic, ctrl).getValue())
336 return imageSamples, skySamples
339 """Solve multiple scales for a single scale factor
341 Having measured samples from the image
and sky frame, we
342 fit
for the scaling factor.
346 scales : `list` of a `tuple` of two `numpy.ndarray` arrays
347 A `list` of the results
from `measureScale` method.
356 for ii, ss
in scales:
357 imageSamples.extend(ii)
358 skySamples.extend(ss)
359 assert len(imageSamples) == len(skySamples)
360 imageSamples = numpy.array(imageSamples)
361 skySamples = numpy.array(skySamples)
364 return afwMath.LeastSquares.fromDesignMatrix(skySamples[mask].reshape(mask.sum(), 1),
366 afwMath.LeastSquares.DIRECT_SVD).getSolution()
368 mask = numpy.isfinite(imageSamples) & numpy.isfinite(skySamples)
369 for ii
in range(self.config.skyIter):
370 solution = solve(mask)
371 residuals = imageSamples - solution*skySamples
372 lq, uq = numpy.percentile(residuals[mask], [25, 75])
373 stdev = 0.741*(uq - lq)
374 with numpy.errstate(invalid=
"ignore"):
375 bad = numpy.abs(residuals) > self.config.skyRej*stdev
381 """Subtract sky frame from science image
387 skyBackground : `lsst.afw.math.BackgroundList`
388 Sky background model.
390 Scale to apply to background model.
391 bgList : `lsst.afw.math.BackgroundList`
392 List of backgrounds applied to image
394 if isinstance(image, afwImage.Exposure):
395 image = image.getMaskedImage()
396 if isinstance(image, afwImage.MaskedImage):
397 image = image.getImage()
398 image.scaledMinus(scale, skyBackground.getImage())
399 if bgList
is not None:
401 bgData = list(skyBackground[0])
403 statsImage = bg.getStatsImage().clone()
405 newBg = afwMath.BackgroundMI(bg.getImageBBox(), statsImage)
406 newBgData = [newBg] + bgData[1:]
407 bgList.append(newBgData)
411 """Interpolate in one dimension
413 Interpolates the curve provided by `xSample` and `ySample` at
414 the positions of `xInterp`. Automatically backs off the
415 interpolation method to achieve successful interpolation.
419 method : `lsst.afw.math.Interpolate.Style`
420 Interpolation method to use.
421 xSample : `numpy.ndarray`
423 ySample : `numpy.ndarray`
424 Vector of coordinates.
425 xInterp : `numpy.ndarray`
426 Vector of ordinates to which to interpolate.
430 yInterp : `numpy.ndarray`
431 Vector of interpolated coordinates.
434 if len(xSample) == 0:
435 return numpy.ones_like(xInterp)*numpy.nan
437 return afwMath.makeInterpolate(xSample.astype(float), ySample.astype(float),
438 method).interpolate(xInterp.astype(float))
440 if method == afwMath.Interpolate.CONSTANT:
442 return numpy.ones_like(xInterp)*numpy.nan
443 newMethod = afwMath.lookupMaxInterpStyle(len(xSample))
444 if newMethod == method:
445 newMethod = afwMath.Interpolate.CONSTANT
450 """Interpolate bad pixels in an image array
452 The bad pixels are modified in the array.
456 array : `numpy.ndarray`
457 Image array
with bad pixels.
458 isBad : `numpy.ndarray` of type `bool`
459 Boolean array indicating which pixels are bad.
460 interpolationStyle : `str`
462 supported values are CONSTANT, LINEAR, NATURAL_SPLINE,
466 raise RuntimeError(
"No good pixels in image array")
467 height, width = array.shape
468 xIndices = numpy.arange(width, dtype=float)
469 yIndices = numpy.arange(height, dtype=float)
470 method = afwMath.stringToInterpStyle(interpolationStyle)
472 for y
in range(height):
473 if numpy.any(isBad[y, :])
and numpy.any(isGood[y, :]):
474 array[y][isBad[y]] =
interpolate1D(method, xIndices[isGood[y]], array[y][isGood[y]],
477 isBad = numpy.isnan(array)
479 for x
in range(width):
480 if numpy.any(isBad[:, x])
and numpy.any(isGood[:, x]):
481 array[:, x][isBad[:, x]] =
interpolate1D(method, yIndices[isGood[:, x]],
482 array[:, x][isGood[:, x]], yIndices[isBad[:, x]])
486 """Configuration for FocalPlaneBackground
488 Note that `xSize` and `ySize` are floating-point values,
as
489 the focal plane frame
is usually defined
in units of microns
490 or millimetres rather than pixels. As such, their values will
491 need to be revised according to each particular camera. For
492 this reason, no defaults are set
for those.
494 xSize = Field(dtype=float, doc="Bin size in x")
495 ySize = Field(dtype=float, doc=
"Bin size in y")
496 pixelSize = Field(dtype=float, default=1.0, doc=
"Pixel size in same units as xSize/ySize")
497 minFrac = Field(dtype=float, default=0.1, doc=
"Minimum fraction of bin size for good measurement")
498 mask = ListField(dtype=str, doc=
"Mask planes to treat as bad",
499 default=[
"BAD",
"SAT",
"INTRP",
"DETECTED",
"DETECTED_NEGATIVE",
"EDGE",
"NO_DATA"])
500 interpolation = ChoiceField(
501 doc=
"how to interpolate the background values. This maps to an enum; see afw::math::Background",
502 dtype=str, default=
"AKIMA_SPLINE", optional=
True,
504 "CONSTANT":
"Use a single constant value",
505 "LINEAR":
"Use linear interpolation",
506 "NATURAL_SPLINE":
"cubic spline with zero second derivative at endpoints",
507 "AKIMA_SPLINE":
"higher-level nonlinear spline that is more robust to outliers",
508 "NONE":
"No background estimation is to be attempted",
511 doSmooth = Field(dtype=bool, default=
False, doc=
"Do smoothing?")
512 smoothScale = Field(dtype=float, default=2.0, doc=
"Smoothing scale, as a multiple of the bin size")
513 binning = Field(dtype=int, default=64, doc=
"Binning to use for CCD background model (pixels)")
517 """Background model for a focal plane camera
519 We model the background empirically with the
"superpixel" method: we
520 measure the background
in each superpixel
and interpolate between
521 superpixels to
yield the model.
524 is that here the superpixels are defined
in the frame of the focal
525 plane of the camera which removes discontinuities across detectors.
527 The constructor you probably want to use
is the `fromCamera` classmethod.
529 There are two use patterns
for building a background model:
531 * Serial: create a `FocalPlaneBackground`, then `addCcd`
for each of the
534 * Parallel: create a `FocalPlaneBackground`, then `clone` it
for each
535 of the CCDs
in an exposure
and use those to `addCcd` their respective
536 CCD image. Finally, `merge` all the clones into the original.
538 Once you
've built the background model, you can apply it to individual
539 CCDs with the `toCcdBackground` method.
543 """Construct from a camera object
547 config : `FocalPlaneBackgroundConfig`
548 Configuration for measuring backgrounds.
550 Camera
for which to measure backgrounds.
554 for point
in ccd.getCorners(afwCameraGeom.FOCAL_PLANE):
555 cameraBox.include(point)
557 width, height = cameraBox.getDimensions()
561 dims =
geom.Extent2I(int(numpy.ceil(width/config.xSize)) + 2,
562 int(numpy.ceil(height/config.ySize)) + 2)
564 transform = (geom.AffineTransform.makeTranslation(
geom.Extent2D(1, 1))
565 * geom.AffineTransform.makeScaling(1.0/config.xSize, 1.0/config.ySize)
566 * geom.AffineTransform.makeTranslation(offset))
568 return cls(config, dims, afwGeom.makeTransform(transform))
572 """Construct from an object that has the same interface.
576 other : `FocalPlaneBackground`-like
577 An object that matches the interface of `FocalPlaneBackground`
578 but which may be different.
582 background : `FocalPlaneBackground`
583 Something guaranteed to be a `FocalPlaneBackground`.
585 return cls(other.config, other.dims, other.transform, other._values, other._numbers)
587 def __init__(self, config, dims, transform, values=None, numbers=None):
590 Developers should note that changes to the signature of this method
591 require coordinated changes to the `__reduce__` and `clone` methods.
595 config : `FocalPlaneBackgroundConfig`
596 Configuration
for measuring backgrounds.
598 Dimensions
for background samples.
600 Transformation
from focal plane coordinates to sample coordinates.
601 values : `lsst.afw.image.ImageF`
602 Measured background values.
603 numbers : `lsst.afw.image.ImageF`
604 Number of pixels
in each background measurement.
611 values = afwImage.ImageF(self.
dims)
614 values = values.clone()
615 assert(values.getDimensions() == self.
dims)
618 numbers = afwImage.ImageF(self.
dims)
621 numbers = numbers.clone()
622 assert(numbers.getDimensions() == self.
dims)
634 We measure the background on the CCD (clipped mean), and record
635 the results
in the model. For simplicity, measurements are made
636 in a box on the CCD corresponding to the warped coordinates of the
637 superpixel rather than accounting
for little rotations, etc.
638 We also record the number of pixels used
in the measurement so we
639 can have a measure of confidence
in each bin
's value.
644 CCD exposure to measure
646 detector = exposure.getDetector()
647 transform = detector.getTransformMap().getTransform(detector.makeCameraSys(afwCameraGeom.PIXELS),
648 detector.makeCameraSys(afwCameraGeom.FOCAL_PLANE))
649 image = exposure.getMaskedImage()
650 maskVal = image.getMask().getPlaneBitMask(self.config.mask)
653 toSample = transform.then(self.
transform)
655 warped = afwImage.ImageF(self.
_values.getBBox())
656 warpedCounts = afwImage.ImageF(self.
_numbers.getBBox())
657 width, height = warped.getDimensions()
659 stats = afwMath.StatisticsControl()
660 stats.setAndMask(maskVal)
661 stats.setNanSafe(
True)
663 pixels = itertools.product(range(width), range(height))
664 for xx, yy
in pixels:
665 llc = toSample.applyInverse(
geom.Point2D(xx - 0.5, yy - 0.5))
666 urc = toSample.applyInverse(
geom.Point2D(xx + 0.5, yy + 0.5))
668 bbox.clip(image.getBBox())
671 subImage = image.Factory(image, bbox)
672 result = afwMath.makeStatistics(subImage, afwMath.MEANCLIP | afwMath.NPOINT, stats)
673 mean = result.getValue(afwMath.MEANCLIP)
674 num = result.getValue(afwMath.NPOINT)
675 if not numpy.isfinite(mean)
or not numpy.isfinite(num):
677 warped[xx, yy, afwImage.LOCAL] = mean*num
678 warpedCounts[xx, yy, afwImage.LOCAL] = num
684 """Produce a background model for a CCD
686 The superpixel background model is warped back to the
687 CCD frame,
for application to the individual CCD.
692 CCD
for which to produce background model.
694 Bounding box of CCD exposure.
698 bg : `lsst.afw.math.BackgroundList`
699 Background model
for CCD.
701 transform = detector.getTransformMap().getTransform(detector.makeCameraSys(afwCameraGeom.PIXELS),
702 detector.makeCameraSys(afwCameraGeom.FOCAL_PLANE))
703 binTransform = (geom.AffineTransform.makeScaling(self.config.binning)
704 * geom.AffineTransform.makeTranslation(geom.Extent2D(0.5, 0.5)))
707 toSample = afwGeom.makeTransform(binTransform).then(transform).then(self.
transform)
710 fpNorm = afwImage.ImageF(focalPlane.getBBox())
713 image = afwImage.ImageF(bbox.getDimensions()//self.
config.binning)
714 norm = afwImage.ImageF(image.getBBox())
715 ctrl = afwMath.WarpingControl(
"bilinear")
716 afwMath.warpImage(image, focalPlane, toSample.inverted(), ctrl)
717 afwMath.warpImage(norm, fpNorm, toSample.inverted(), ctrl)
720 mask = afwImage.Mask(image.getBBox())
721 isBad = numpy.isnan(image.getArray())
722 mask.getArray()[isBad] = mask.getPlaneBitMask(
"BAD")
723 image.getArray()[isBad] = image.getArray()[~isBad].mean()
725 return afwMath.BackgroundList(
726 (afwMath.BackgroundMI(bbox, afwImage.makeMaskedImage(image, mask)),
727 afwMath.stringToInterpStyle(self.
config.interpolation),
728 afwMath.stringToUndersampleStyle(
"REDUCE_INTERP_ORDER"),
729 afwMath.ApproximateControl.UNKNOWN,
734 """Merge with another FocalPlaneBackground
736 This allows multiple background models to be constructed from
737 different CCDs,
and then merged to form a single consistent
738 background model
for the entire focal plane.
742 other : `FocalPlaneBackground`
743 Another background model to merge.
747 self : `FocalPlaneBackground`
748 The merged background model.
750 if (self.
config.xSize, self.
config.ySize) != (other.config.xSize, other.config.ySize):
751 raise RuntimeError(
"Size mismatch: %s vs %s" % ((self.
config.xSize, self.
config.ySize),
752 (other.config.xSize, other.config.ySize)))
753 if self.
dims != other.dims:
754 raise RuntimeError(
"Dimensions mismatch: %s vs %s" % (self.
dims, other.dims))
760 """Merge with another FocalPlaneBackground
764 other : `FocalPlaneBackground`
765 Another background model to merge.
769 self : `FocalPlaneBackground`
770 The merged background model.
772 return self.
merge(other)
775 """Return the background model data
777 This is the measurement of the background
for each of the superpixels.
781 thresh = (self.config.minFrac
783 isBad = self._numbers.getArray() < thresh
785 array = values.getArray()
787 isBad = numpy.isnan(values.array)
794 """Configuration for MaskObjectsTask"""
795 nIter = Field(dtype=int, default=3, doc=
"Number of iterations")
796 subtractBackground = ConfigurableField(target=measAlg.SubtractBackgroundTask,
797 doc=
"Background subtraction")
798 detection = ConfigurableField(target=measAlg.SourceDetectionTask, doc=
"Source detection")
799 detectSigma = Field(dtype=float, default=5.0, doc=
"Detection threshold (standard deviations)")
800 doInterpolate = Field(dtype=bool, default=
True, doc=
"Interpolate when removing objects?")
801 interpolate = ConfigurableField(target=measAlg.SubtractBackgroundTask, doc=
"Interpolation")
804 self.
detection.reEstimateBackground =
False
805 self.
detection.doTempLocalBackground =
False
806 self.
detection.doTempWideBackground =
False
817 raise RuntimeError(
"Incorrect settings for object masking: reEstimateBackground, "
818 "doTempLocalBackground and doTempWideBackground must be False")
822 """Iterative masking of objects on an Exposure
824 This task makes more exhaustive object mask by iteratively doing detection
825 and background-subtraction. The purpose of this task
is to get true
826 background removing faint tails of large objects. This
is useful to get a
827 clean sky estimate
from relatively small number of visits.
829 We deliberately use the specified ``detectSigma`` instead of the PSF,
830 in order to better pick up the faint wings of objects.
832 ConfigClass = MaskObjectsConfig
837 self.makeSubtask(
"detection", schema=afwTable.Schema())
838 self.makeSubtask(
"interpolate")
839 self.makeSubtask(
"subtractBackground")
841 def run(self, exposure, maskPlanes=None):
842 """Mask objects on Exposure
844 Objects are found and removed.
849 Exposure on which to mask objects.
850 maskPlanes : iterable of `str`, optional
851 List of mask planes to remove.
857 """Iteratively find objects on an exposure
859 Objects are masked with the ``DETECTED`` mask plane.
864 Exposure on which to mask objects.
866 for _
in range(self.config.nIter):
867 bg = self.subtractBackground.
run(exposure).background
868 self.detection.detectFootprints(exposure, sigma=self.config.detectSigma, clearMask=
True)
869 exposure.maskedImage += bg.getImage()
872 """Remove objects from exposure
874 We interpolate over using a background model if ``doInterpolate``
is
875 set; otherwise we simply replace everything
with the median.
880 Exposure on which to mask objects.
881 maskPlanes : iterable of `str`, optional
882 List of mask planes to remove. ``DETECTED`` will be added
as well.
884 image = exposure.image
886 maskVal = mask.getPlaneBitMask("DETECTED")
887 if maskPlanes
is not None:
888 maskVal |= mask.getPlaneBitMask(maskPlanes)
889 isBad = mask.array & maskVal > 0
891 if self.config.doInterpolate:
892 smooth = self.interpolate.fitBackground(exposure.maskedImage)
893 replace = smooth.getImageF().array[isBad]
894 mask.array &= ~mask.getPlaneBitMask([
"DETECTED"])
896 replace = numpy.median(image.array[~isBad])
897 image.array[isBad] = replace
901 """Gaussian-smooth an array while ignoring bad pixels
903 It's not sufficient to set the bad pixels to zero, as then they're treated
904 as if they are zero, rather than being ignored altogether. We need to apply
905 a correction to that image that removes the effect of the bad pixels.
909 array : `numpy.ndarray` of floating-point
911 bad : `numpy.ndarray` of `bool`
912 Flag array indicating bad pixels.
918 convolved : `numpy.ndarray`
921 convolved = gaussian_filter(numpy.where(bad, 0.0, array), sigma, mode="constant", cval=0.0)
922 denominator = gaussian_filter(numpy.where(bad, 0.0, 1.0), sigma, mode=
"constant", cval=0.0)
923 return convolved/denominator
926def _create_module_child(name):
927 """Create an empty module attached to the relevant parent."""
928 parent, child = name.rsplit(
".", 1)
929 spec = importlib.machinery.ModuleSpec(name,
None)
930 newmod = importlib.util.module_from_spec(spec)
931 setattr(sys.modules[parent], child, newmod)
932 sys.modules[name] = newmod
941 import lsst.pipe.drivers.background
944 pipe_drivers = _create_module_child(
"lsst.pipe.drivers")
947 pipe_drivers_background = _create_module_child(
"lsst.pipe.drivers.background")
950 setattr(pipe_drivers_background, FocalPlaneBackground.__name__, FocalPlaneBackground)
951 setattr(pipe_drivers_background, FocalPlaneBackgroundConfig.__name__, FocalPlaneBackgroundConfig)
def addCcd(self, exposure)
def fromSimilar(cls, other)
def __init__(self, config, dims, transform, values=None, numbers=None)
def fromCamera(cls, config, camera)
def __iadd__(self, other)
def toCcdBackground(self, detector, bbox)
def __init__(self, *args, **kwargs)
def removeObjects(self, exposure, maskPlanes=None)
def findObjects(self, exposure)
def run(self, exposure, maskPlanes=None)
def measureScale(self, image, skyBackground)
def subtractSkyFrame(self, image, skyBackground, scale, bgList=None)
def averageBackgrounds(self, bgList)
def backgroundToExposure(self, statsImage, bbox)
def getSkyData(self, butler, calibId)
def exposureToBackground(bgExp)
def solveScales(self, scales)
def measureBackground(self, image)
def robustMean(array, rej=3.0)
def interpolate1D(method, xSample, ySample, xInterp)
def smoothArray(array, bad, sigma)
def interpolateBadPixels(array, isBad, interpolationStyle)