3from scipy.ndimage
import gaussian_filter
4from deprecated.sphinx
import deprecated
14from lsst.pex.config import Config, Field, ListField, ChoiceField, ConfigField, RangeField, ConfigurableField
15from lsst.pipe.base
import Task
19 """Measure a robust mean of an array
23 array : `numpy.ndarray`
24 Array for which to measure the mean.
26 k-sigma rejection threshold.
31 Robust mean of `array`.
33 q1, median, q3 = numpy.percentile(array, [25.0, 50.0, 100.0])
34 good = numpy.abs(array - median) < rej*0.74*(q3 - q1)
35 return array[good].mean()
39 """Configuration for background measurement"""
40 statistic = ChoiceField(dtype=str, default=
"MEANCLIP", doc=
"type of statistic to use for grid points",
41 allowed={
"MEANCLIP":
"clipped mean",
42 "MEAN":
"unclipped mean",
44 xBinSize = RangeField(dtype=int, default=32, min=1, doc=
"Superpixel size in x")
45 yBinSize = RangeField(dtype=int, default=32, min=1, doc=
"Superpixel size in y")
46 algorithm = ChoiceField(dtype=str, default=
"NATURAL_SPLINE", optional=
True,
47 doc=
"How to interpolate the background values. "
48 "This maps to an enum; see afw::math::Background",
50 "CONSTANT":
"Use a single constant value",
51 "LINEAR":
"Use linear interpolation",
52 "NATURAL_SPLINE":
"cubic spline with zero second derivative at endpoints",
53 "AKIMA_SPLINE":
"higher-level nonlinear spline that is more robust"
55 "NONE":
"No background estimation is to be attempted",
57 mask = ListField(dtype=str, default=[
"SAT",
"BAD",
"EDGE",
"DETECTED",
"DETECTED_NEGATIVE",
"NO_DATA"],
58 doc=
"Names of mask planes to ignore while estimating the background")
62 """Parameters controlling the measurement of sky statistics"""
63 statistic = ChoiceField(dtype=str, default=
"MEANCLIP", doc=
"type of statistic to use for grid points",
64 allowed={
"MEANCLIP":
"clipped mean",
65 "MEAN":
"unclipped mean",
67 clip = Field(doc=
"Clipping threshold for background", dtype=float, default=3.0)
68 nIter = Field(doc=
"Clipping iterations for background", dtype=int, default=3)
69 mask = ListField(doc=
"Mask planes to reject", dtype=str,
70 default=[
"SAT",
"DETECTED",
"DETECTED_NEGATIVE",
"BAD",
"NO_DATA"])
74 """Configuration for SkyMeasurementTask"""
75 skyIter = Field(dtype=int, default=3, doc=
"k-sigma rejection iterations for sky scale")
76 skyRej = Field(dtype=float, default=3.0, doc=
"k-sigma rejection threshold for sky scale")
77 background = ConfigField(dtype=BackgroundConfig, doc=
"Background measurement")
78 xNumSamples = Field(dtype=int, default=4, doc=
"Number of samples in x for scaling sky frame")
79 yNumSamples = Field(dtype=int, default=4, doc=
"Number of samples in y for scaling sky frame")
80 stats = ConfigField(dtype=SkyStatsConfig, doc=
"Measurement of sky statistics in the samples")
84 """Task for creating, persisting and using sky frames
86 A sky frame is like a fringe frame (the sum of many exposures of the night sky,
87 combined
with rejection to remove astrophysical objects)
except the structure
88 is on larger scales,
and hence we bin the images
and represent them
as a
90 the dominant response of the camera to the sky background.
92 ConfigClass = SkyMeasurementConfig
95 """Retrieve sky frame from the butler
99 butler : `lsst.daf.persistence.Butler`
102 Data identifier for calib
106 sky : `lsst.afw.math.BackgroundList`
109 exp = butler.get("sky", calibId)
114 """Convert an exposure to background model
116 Calibs need to be persisted as an Exposure, so we need to convert
117 the persisted Exposure to a background model.
122 Background model
in Exposure format.
126 bg : `lsst.afw.math.BackgroundList`
129 header = bgExp.getMetadata()
130 xMin = header.getScalar("BOX.MINX")
131 yMin = header.getScalar(
"BOX.MINY")
132 xMax = header.getScalar(
"BOX.MAXX")
133 yMax = header.getScalar(
"BOX.MAXY")
134 algorithm = header.getScalar(
"ALGORITHM")
136 return afwMath.BackgroundList(
137 (afwMath.BackgroundMI(bbox, bgExp.getMaskedImage()),
138 afwMath.stringToInterpStyle(algorithm),
139 afwMath.stringToUndersampleStyle(
"REDUCE_INTERP_ORDER"),
140 afwMath.ApproximateControl.UNKNOWN,
144 """Convert a background model to an exposure
146 Calibs need to be persisted as an Exposure, so we need to convert
147 the background model to an Exposure.
151 statsImage : `lsst.afw.image.MaskedImageF`
152 Background model
's statistics image.
154 Bounding box for image.
159 Background model
in Exposure format.
161 exp = afwImage.makeExposure(statsImage)
162 header = exp.getMetadata()
163 header.set("BOX.MINX", bbox.getMinX())
164 header.set(
"BOX.MINY", bbox.getMinY())
165 header.set(
"BOX.MAXX", bbox.getMaxX())
166 header.set(
"BOX.MAXY", bbox.getMaxY())
167 header.set(
"ALGORITHM", self.config.background.algorithm)
171 """Measure a background model for image
173 This doesn't use a full-featured background model (e.g., no Chebyshev
174 approximation) because we just want the binning behaviour. This will
175 allow us to average the bins later (`averageBackgrounds`).
177 The `BackgroundMI` is wrapped
in a `BackgroundList` so it can be
178 pickled
and persisted.
183 Image
for which to measure background.
187 bgModel : `lsst.afw.math.BackgroundList`
190 stats = afwMath.StatisticsControl()
191 stats.setAndMask(image.getMask().getPlaneBitMask(self.config.background.mask))
192 stats.setNanSafe(True)
193 ctrl = afwMath.BackgroundControl(
194 self.config.background.algorithm,
195 max(
int(image.getWidth()/self.config.background.xBinSize + 0.5), 1),
196 max(
int(image.getHeight()/self.config.background.yBinSize + 0.5), 1),
197 "REDUCE_INTERP_ORDER",
199 self.config.background.statistic
202 bg = afwMath.makeBackground(image, ctrl)
204 return afwMath.BackgroundList((
206 afwMath.stringToInterpStyle(self.config.background.algorithm),
207 afwMath.stringToUndersampleStyle(
"REDUCE_INTERP_ORDER"),
208 afwMath.ApproximateControl.UNKNOWN,
213 """Average multiple background models
215 The input background models should be a `BackgroundList` consisting
216 of a single `BackgroundMI`.
220 bgList : `list` of `lsst.afw.math.BackgroundList`
221 Background models to average.
226 Background model in Exposure format.
228 assert all(len(bg) == 1
for bg
in bgList),
"Mixed bgList: %s" % ([len(bg)
for bg
in bgList],)
229 images = [bg[0][0].getStatsImage()
for bg
in bgList]
230 boxes = [bg[0][0].getImageBBox()
for bg
in bgList]
231 assert len(set((box.getMinX(), box.getMinY(), box.getMaxX(), box.getMaxY())
for box
in boxes)) == 1, \
232 "Bounding boxes not all equal"
236 maskVal = afwImage.Mask.getPlaneBitMask(
"BAD")
238 bad = numpy.isnan(img.getImage().getArray())
239 img.getMask().getArray()[bad] = maskVal
241 stats = afwMath.StatisticsControl()
242 stats.setAndMask(maskVal)
243 stats.setNanSafe(
True)
244 combined = afwMath.statisticsStack(images, afwMath.MEANCLIP, stats)
250 array = combined.getImage().getArray()
251 bad = numpy.isnan(array)
252 median = numpy.median(array[~bad])
259 """Measure scale of background model in image
261 We treat the sky frame much as we would a fringe frame
262 (
except the length scale of the variations
is different):
263 we measure samples on the input image
and the sky frame,
264 which we will use to determine the scaling factor
in the
265 'solveScales` method.
270 Science image
for which to measure scale.
271 skyBackground : `lsst.afw.math.BackgroundList`
272 Sky background model.
276 imageSamples : `numpy.ndarray`
277 Sample measurements on image.
278 skySamples : `numpy.ndarray`
279 Sample measurements on sky frame.
281 if isinstance(image, afwImage.Exposure):
282 image = image.getMaskedImage()
284 xNumSamples = min(self.config.xNumSamples, image.getWidth())
285 yNumSamples = min(self.config.yNumSamples, image.getHeight())
286 xLimits = numpy.linspace(0, image.getWidth(), xNumSamples + 1, dtype=int)
287 yLimits = numpy.linspace(0, image.getHeight(), yNumSamples + 1, dtype=int)
288 sky = skyBackground.getImage()
289 maskVal = image.getMask().getPlaneBitMask(self.config.stats.mask)
290 ctrl = afwMath.StatisticsControl(self.config.stats.clip, self.config.stats.nIter, maskVal)
291 statistic = afwMath.stringToStatisticsProperty(self.config.stats.statistic)
294 for xIndex, yIndex
in itertools.product(range(xNumSamples), range(yNumSamples)):
296 xStart, xStop = xLimits[xIndex], xLimits[xIndex + 1] - 1
297 yStart, yStop = yLimits[yIndex], yLimits[yIndex + 1] - 1
299 subImage = image.Factory(image, box)
300 subSky = sky.Factory(sky, box)
301 imageSamples.append(afwMath.makeStatistics(subImage, statistic, ctrl).getValue())
302 skySamples.append(afwMath.makeStatistics(subSky, statistic, ctrl).getValue())
303 return imageSamples, skySamples
306 """Solve multiple scales for a single scale factor
308 Having measured samples from the image
and sky frame, we
309 fit
for the scaling factor.
313 scales : `list` of a `tuple` of two `numpy.ndarray` arrays
314 A `list` of the results
from `measureScale` method.
323 for ii, ss
in scales:
324 imageSamples.extend(ii)
325 skySamples.extend(ss)
326 assert len(imageSamples) == len(skySamples)
327 imageSamples = numpy.array(imageSamples)
328 skySamples = numpy.array(skySamples)
331 return afwMath.LeastSquares.fromDesignMatrix(skySamples[mask].reshape(mask.sum(), 1),
333 afwMath.LeastSquares.DIRECT_SVD).getSolution()
335 mask = numpy.isfinite(imageSamples) & numpy.isfinite(skySamples)
336 for ii
in range(self.config.skyIter):
337 solution = solve(mask)
338 residuals = imageSamples - solution*skySamples
339 lq, uq = numpy.percentile(residuals[mask], [25, 75])
340 stdev = 0.741*(uq - lq)
341 with numpy.errstate(invalid=
"ignore"):
342 bad = numpy.abs(residuals) > self.config.skyRej*stdev
348 """Subtract sky frame from science image
354 skyBackground : `lsst.afw.math.BackgroundList`
355 Sky background model.
357 Scale to apply to background model.
358 bgList : `lsst.afw.math.BackgroundList`
359 List of backgrounds applied to image
361 if isinstance(image, afwImage.Exposure):
362 image = image.getMaskedImage()
363 if isinstance(image, afwImage.MaskedImage):
364 image = image.getImage()
365 image.scaledMinus(scale, skyBackground.getImage())
366 if bgList
is not None:
368 bgData = list(skyBackground[0])
370 statsImage = bg.getStatsImage().clone()
372 newBg = afwMath.BackgroundMI(bg.getImageBBox(), statsImage)
373 newBgData = [newBg] + bgData[1:]
374 bgList.append(newBgData)
378 """Interpolate in one dimension
380 Interpolates the curve provided by `xSample` and `ySample` at
381 the positions of `xInterp`. Automatically backs off the
382 interpolation method to achieve successful interpolation.
386 method : `lsst.afw.math.Interpolate.Style`
387 Interpolation method to use.
388 xSample : `numpy.ndarray`
390 ySample : `numpy.ndarray`
391 Vector of coordinates.
392 xInterp : `numpy.ndarray`
393 Vector of ordinates to which to interpolate.
397 yInterp : `numpy.ndarray`
398 Vector of interpolated coordinates.
401 if len(xSample) == 0:
402 return numpy.ones_like(xInterp)*numpy.nan
404 return afwMath.makeInterpolate(xSample.astype(float), ySample.astype(float),
405 method).interpolate(xInterp.astype(float))
407 if method == afwMath.Interpolate.CONSTANT:
409 return numpy.ones_like(xInterp)*numpy.nan
410 newMethod = afwMath.lookupMaxInterpStyle(len(xSample))
411 if newMethod == method:
412 newMethod = afwMath.Interpolate.CONSTANT
417 """Interpolate bad pixels in an image array
419 The bad pixels are modified in the array.
423 array : `numpy.ndarray`
424 Image array
with bad pixels.
425 isBad : `numpy.ndarray` of type `bool`
426 Boolean array indicating which pixels are bad.
427 interpolationStyle : `str`
429 supported values are CONSTANT, LINEAR, NATURAL_SPLINE,
433 raise RuntimeError(
"No good pixels in image array")
434 height, width = array.shape
435 xIndices = numpy.arange(width, dtype=float)
436 yIndices = numpy.arange(height, dtype=float)
437 method = afwMath.stringToInterpStyle(interpolationStyle)
439 for y
in range(height):
440 if numpy.any(isBad[y, :])
and numpy.any(isGood[y, :]):
441 array[y][isBad[y]] =
interpolate1D(method, xIndices[isGood[y]], array[y][isGood[y]],
444 isBad = numpy.isnan(array)
446 for x
in range(width):
447 if numpy.any(isBad[:, x])
and numpy.any(isGood[:, x]):
448 array[:, x][isBad[:, x]] =
interpolate1D(method, yIndices[isGood[:, x]],
449 array[:, x][isGood[:, x]], yIndices[isBad[:, x]])
453 """Configuration for FocalPlaneBackground
455 Note that `xSize` and `ySize` are floating-point values,
as
456 the focal plane frame
is usually defined
in units of microns
457 or millimetres rather than pixels. As such, their values will
458 need to be revised according to each particular camera. For
459 this reason, no defaults are set
for those.
461 xSize = Field(dtype=float, doc="Bin size in x")
462 ySize = Field(dtype=float, doc=
"Bin size in y")
463 pixelSize = Field(dtype=float, default=1.0, doc=
"Pixel size in same units as xSize/ySize")
464 minFrac = Field(dtype=float, default=0.1, doc=
"Minimum fraction of bin size for good measurement")
465 mask = ListField(dtype=str, doc=
"Mask planes to treat as bad",
466 default=[
"BAD",
"SAT",
"INTRP",
"DETECTED",
"DETECTED_NEGATIVE",
"EDGE",
"NO_DATA"])
467 interpolation = ChoiceField(
468 doc=
"how to interpolate the background values. This maps to an enum; see afw::math::Background",
469 dtype=str, default=
"AKIMA_SPLINE", optional=
True,
471 "CONSTANT":
"Use a single constant value",
472 "LINEAR":
"Use linear interpolation",
473 "NATURAL_SPLINE":
"cubic spline with zero second derivative at endpoints",
474 "AKIMA_SPLINE":
"higher-level nonlinear spline that is more robust to outliers",
475 "NONE":
"No background estimation is to be attempted",
478 doSmooth = Field(dtype=bool, default=
False, doc=
"Do smoothing?")
479 smoothScale = Field(dtype=float, default=2.0, doc=
"Smoothing scale, as a multiple of the bin size")
480 binning = Field(dtype=int, default=64, doc=
"Binning to use for CCD background model (pixels)")
484 reason=
"pipe_drivers is deprecated. It will be removed after v25. "
485 "Please use lsst.pipe.tasks.background.FocalPlaneBackground instead.",
487 category=FutureWarning,
490 """Background model for a focal plane camera
492 We model the background empirically with the
"superpixel" method: we
493 measure the background
in each superpixel
and interpolate between
494 superpixels to
yield the model.
497 is that here the superpixels are defined
in the frame of the focal
498 plane of the camera which removes discontinuities across detectors.
500 The constructor you probably want to use
is the `fromCamera` classmethod.
502 There are two use patterns
for building a background model:
504 * Serial: create a `FocalPlaneBackground`, then `addCcd`
for each of the
507 * Parallel: create a `FocalPlaneBackground`, then `clone` it
for each
508 of the CCDs
in an exposure
and use those to `addCcd` their respective
509 CCD image. Finally, `merge` all the clones into the original.
511 Once you
've built the background model, you can apply it to individual
512 CCDs with the `toCcdBackground` method.
516 """Construct from a camera object
520 config : `FocalPlaneBackgroundConfig`
521 Configuration for measuring backgrounds.
523 Camera
for which to measure backgrounds.
527 for point
in ccd.getCorners(afwCameraGeom.FOCAL_PLANE):
528 cameraBox.include(point)
530 width, height = cameraBox.getDimensions()
535 int(numpy.ceil(height/config.ySize)) + 2)
537 transform = (geom.AffineTransform.makeTranslation(
geom.Extent2D(1, 1))*
538 geom.AffineTransform.makeScaling(1.0/config.xSize, 1.0/config.ySize)*
539 geom.AffineTransform.makeTranslation(offset))
541 return cls(config, dims, afwGeom.makeTransform(transform))
543 def __init__(self, config, dims, transform, values=None, numbers=None):
546 Developers should note that changes to the signature of this method
547 require coordinated changes to the `__reduce__` and `clone` methods.
551 config : `FocalPlaneBackgroundConfig`
552 Configuration
for measuring backgrounds.
554 Dimensions
for background samples.
556 Transformation
from focal plane coordinates to sample coordinates.
557 values : `lsst.afw.image.ImageF`
558 Measured background values.
559 numbers : `lsst.afw.image.ImageF`
560 Number of pixels
in each background measurement.
567 values = afwImage.ImageF(self.
dims)
570 values = values.clone()
571 assert(values.getDimensions() == self.
dims)
574 numbers = afwImage.ImageF(self.
dims)
577 numbers = numbers.clone()
578 assert(numbers.getDimensions() == self.
dims)
590 We measure the background on the CCD (clipped mean), and record
591 the results
in the model. For simplicity, measurements are made
592 in a box on the CCD corresponding to the warped coordinates of the
593 superpixel rather than accounting
for little rotations, etc.
594 We also record the number of pixels used
in the measurement so we
595 can have a measure of confidence
in each bin
's value.
600 CCD exposure to measure
602 detector = exposure.getDetector()
603 transform = detector.getTransformMap().getTransform(detector.makeCameraSys(afwCameraGeom.PIXELS),
604 detector.makeCameraSys(afwCameraGeom.FOCAL_PLANE))
605 image = exposure.getMaskedImage()
606 maskVal = image.getMask().getPlaneBitMask(self.config.mask)
609 toSample = transform.then(self.
transform)
611 warped = afwImage.ImageF(self.
_values.getBBox())
612 warpedCounts = afwImage.ImageF(self.
_numbers.getBBox())
613 width, height = warped.getDimensions()
615 stats = afwMath.StatisticsControl()
616 stats.setAndMask(maskVal)
617 stats.setNanSafe(
True)
619 pixels = itertools.product(range(width), range(height))
620 for xx, yy
in pixels:
621 llc = toSample.applyInverse(
geom.Point2D(xx - 0.5, yy - 0.5))
622 urc = toSample.applyInverse(
geom.Point2D(xx + 0.5, yy + 0.5))
624 bbox.clip(image.getBBox())
627 subImage = image.Factory(image, bbox)
628 result = afwMath.makeStatistics(subImage, afwMath.MEANCLIP | afwMath.NPOINT, stats)
629 mean = result.getValue(afwMath.MEANCLIP)
630 num = result.getValue(afwMath.NPOINT)
631 if not numpy.isfinite(mean)
or not numpy.isfinite(num):
633 warped[xx, yy, afwImage.LOCAL] = mean*num
634 warpedCounts[xx, yy, afwImage.LOCAL] = num
640 """Produce a background model for a CCD
642 The superpixel background model is warped back to the
643 CCD frame,
for application to the individual CCD.
648 CCD
for which to produce background model.
650 Bounding box of CCD exposure.
654 bg : `lsst.afw.math.BackgroundList`
655 Background model
for CCD.
657 transform = detector.getTransformMap().getTransform(detector.makeCameraSys(afwCameraGeom.PIXELS),
658 detector.makeCameraSys(afwCameraGeom.FOCAL_PLANE))
659 binTransform = (geom.AffineTransform.makeScaling(self.config.binning)*
660 geom.AffineTransform.makeTranslation(geom.Extent2D(0.5, 0.5)))
663 toSample = afwGeom.makeTransform(binTransform).then(transform).then(self.
transform)
666 fpNorm = afwImage.ImageF(focalPlane.getBBox())
669 image = afwImage.ImageF(bbox.getDimensions()//self.
config.binning)
670 norm = afwImage.ImageF(image.getBBox())
671 ctrl = afwMath.WarpingControl(
"bilinear")
672 afwMath.warpImage(image, focalPlane, toSample.inverted(), ctrl)
673 afwMath.warpImage(norm, fpNorm, toSample.inverted(), ctrl)
676 mask = afwImage.Mask(image.getBBox())
677 isBad = numpy.isnan(image.getArray())
678 mask.getArray()[isBad] = mask.getPlaneBitMask(
"BAD")
679 image.getArray()[isBad] = image.getArray()[~isBad].mean()
681 return afwMath.BackgroundList(
682 (afwMath.BackgroundMI(bbox, afwImage.makeMaskedImage(image, mask)),
683 afwMath.stringToInterpStyle(self.
config.interpolation),
684 afwMath.stringToUndersampleStyle(
"REDUCE_INTERP_ORDER"),
685 afwMath.ApproximateControl.UNKNOWN,
690 """Merge with another FocalPlaneBackground
692 This allows multiple background models to be constructed from
693 different CCDs,
and then merged to form a single consistent
694 background model
for the entire focal plane.
698 other : `FocalPlaneBackground`
699 Another background model to merge.
703 self : `FocalPlaneBackground`
704 The merged background model.
706 if (self.
config.xSize, self.
config.ySize) != (other.config.xSize, other.config.ySize):
707 raise RuntimeError(
"Size mismatch: %s vs %s" % ((self.
config.xSize, self.
config.ySize),
708 (other.config.xSize, other.config.ySize)))
709 if self.
dims != other.dims:
710 raise RuntimeError(
"Dimensions mismatch: %s vs %s" % (self.
dims, other.dims))
716 """Merge with another FocalPlaneBackground
720 other : `FocalPlaneBackground`
721 Another background model to merge.
725 self : `FocalPlaneBackground`
726 The merged background model.
728 return self.
merge(other)
731 """Return the background model data
733 This is the measurement of the background
for each of the superpixels.
737 thresh = (self.config.minFrac*
739 isBad = self._numbers.getArray() < thresh
741 array = values.getArray()
743 isBad = numpy.isnan(values.array)
750 """Configuration for MaskObjectsTask"""
751 nIter = Field(dtype=int, default=3, doc=
"Number of iterations")
752 subtractBackground = ConfigurableField(target=measAlg.SubtractBackgroundTask,
753 doc=
"Background subtraction")
754 detection = ConfigurableField(target=measAlg.SourceDetectionTask, doc=
"Source detection")
755 detectSigma = Field(dtype=float, default=5.0, doc=
"Detection threshold (standard deviations)")
756 doInterpolate = Field(dtype=bool, default=
True, doc=
"Interpolate when removing objects?")
757 interpolate = ConfigurableField(target=measAlg.SubtractBackgroundTask, doc=
"Interpolation")
760 self.
detection.reEstimateBackground =
False
761 self.
detection.doTempLocalBackground =
False
762 self.
detection.doTempWideBackground =
False
770 if (self.
detection.reEstimateBackground
or
773 raise RuntimeError(
"Incorrect settings for object masking: reEstimateBackground, "
774 "doTempLocalBackground and doTempWideBackground must be False")
778 """Iterative masking of objects on an Exposure
780 This task makes more exhaustive object mask by iteratively doing detection
781 and background-subtraction. The purpose of this task
is to get true
782 background removing faint tails of large objects. This
is useful to get a
783 clean sky estimate
from relatively small number of visits.
785 We deliberately use the specified ``detectSigma`` instead of the PSF,
786 in order to better pick up the faint wings of objects.
788 ConfigClass = MaskObjectsConfig
793 self.makeSubtask(
"detection", schema=afwTable.Schema())
794 self.makeSubtask(
"interpolate")
795 self.makeSubtask(
"subtractBackground")
797 def run(self, exposure, maskPlanes=None):
798 """Mask objects on Exposure
800 Objects are found and removed.
805 Exposure on which to mask objects.
806 maskPlanes : iterable of `str`, optional
807 List of mask planes to remove.
813 """Iteratively find objects on an exposure
815 Objects are masked with the ``DETECTED`` mask plane.
820 Exposure on which to mask objects.
822 for _
in range(self.config.nIter):
823 bg = self.subtractBackground.
run(exposure).background
824 self.detection.detectFootprints(exposure, sigma=self.config.detectSigma, clearMask=
True)
825 exposure.maskedImage += bg.getImage()
828 """Remove objects from exposure
830 We interpolate over using a background model if ``doInterpolate``
is
831 set; otherwise we simply replace everything
with the median.
836 Exposure on which to mask objects.
837 maskPlanes : iterable of `str`, optional
838 List of mask planes to remove. ``DETECTED`` will be added
as well.
840 image = exposure.image
842 maskVal = mask.getPlaneBitMask("DETECTED")
843 if maskPlanes
is not None:
844 maskVal |= mask.getPlaneBitMask(maskPlanes)
845 isBad = mask.array & maskVal > 0
847 if self.config.doInterpolate:
848 smooth = self.interpolate.fitBackground(exposure.maskedImage)
849 replace = smooth.getImageF().array[isBad]
850 mask.array &= ~mask.getPlaneBitMask([
"DETECTED"])
852 replace = numpy.median(image.array[~isBad])
853 image.array[isBad] = replace
857 """Gaussian-smooth an array while ignoring bad pixels
859 It's not sufficient to set the bad pixels to zero, as then they're treated
860 as if they are zero, rather than being ignored altogether. We need to apply
861 a correction to that image that removes the effect of the bad pixels.
865 array : `numpy.ndarray` of floating-point
867 bad : `numpy.ndarray` of `bool`
868 Flag array indicating bad pixels.
874 convolved : `numpy.ndarray`
877 convolved = gaussian_filter(numpy.where(bad, 0.0, array), sigma, mode="constant", cval=0.0)
878 denominator = gaussian_filter(numpy.where(bad, 0.0, 1.0), sigma, mode=
"constant", cval=0.0)
879 return convolved/denominator
def fromCamera(cls, config, camera)
def toCcdBackground(self, detector, bbox)
def __iadd__(self, other)
def addCcd(self, exposure)
def __init__(self, config, dims, transform, values=None, numbers=None)
def __init__(self, *args, **kwargs)
def removeObjects(self, exposure, maskPlanes=None)
def run(self, exposure, maskPlanes=None)
def findObjects(self, exposure)
def exposureToBackground(bgExp)
def backgroundToExposure(self, statsImage, bbox)
def measureBackground(self, image)
def solveScales(self, scales)
def subtractSkyFrame(self, image, skyBackground, scale, bgList=None)
def getSkyData(self, butler, calibId)
def measureScale(self, image, skyBackground)
def averageBackgrounds(self, bgList)
def interpolate1D(method, xSample, ySample, xInterp)
def interpolateBadPixels(array, isBad, interpolationStyle)
def smoothArray(array, bad, sigma)
def robustMean(array, rej=3.0)