3 from scipy.ndimage 
import gaussian_filter
 
   13 from lsst.pex.config 
import Config, Field, ListField, ChoiceField, ConfigField, RangeField, ConfigurableField
 
   18     """Measure a robust mean of an array 
   22     array : `numpy.ndarray` 
   23         Array for which to measure the mean. 
   25         k-sigma rejection threshold. 
   30         Robust mean of `array`. 
   32     q1, median, q3 = numpy.percentile(array, [25.0, 50.0, 100.0])
 
   33     good = numpy.abs(array - median) < rej*0.74*(q3 - q1)
 
   34     return array[good].mean()
 
   38     """Configuration for background measurement""" 
   39     statistic = ChoiceField(dtype=str, default=
"MEANCLIP", doc=
"type of statistic to use for grid points",
 
   40                             allowed={
"MEANCLIP": 
"clipped mean",
 
   41                                      "MEAN": 
"unclipped mean",
 
   43     xBinSize = RangeField(dtype=int, default=32, min=1, doc=
"Superpixel size in x")
 
   44     yBinSize = RangeField(dtype=int, default=32, min=1, doc=
"Superpixel size in y")
 
   45     algorithm = ChoiceField(dtype=str, default=
"NATURAL_SPLINE", optional=
True,
 
   46                             doc=
"How to interpolate the background values. " 
   47                                 "This maps to an enum; see afw::math::Background",
 
   49                                 "CONSTANT": 
"Use a single constant value",
 
   50                                 "LINEAR": 
"Use linear interpolation",
 
   51                                 "NATURAL_SPLINE": 
"cubic spline with zero second derivative at endpoints",
 
   52                                 "AKIMA_SPLINE": 
"higher-level nonlinear spline that is more robust" 
   54                                 "NONE": 
"No background estimation is to be attempted",
 
   56     mask = ListField(dtype=str, default=[
"SAT", 
"BAD", 
"EDGE", 
"DETECTED", 
"DETECTED_NEGATIVE", 
"NO_DATA"],
 
   57                      doc=
"Names of mask planes to ignore while estimating the background")
 
   61     """Parameters controlling the measurement of sky statistics""" 
   62     statistic = ChoiceField(dtype=str, default=
"MEANCLIP", doc=
"type of statistic to use for grid points",
 
   63                             allowed={
"MEANCLIP": 
"clipped mean",
 
   64                                      "MEAN": 
"unclipped mean",
 
   66     clip = Field(doc=
"Clipping threshold for background", dtype=float, default=3.0)
 
   67     nIter = Field(doc=
"Clipping iterations for background", dtype=int, default=3)
 
   68     mask = ListField(doc=
"Mask planes to reject", dtype=str,
 
   69                      default=[
"SAT", 
"DETECTED", 
"DETECTED_NEGATIVE", 
"BAD", 
"NO_DATA"])
 
   73     """Configuration for SkyMeasurementTask""" 
   74     skyIter = Field(dtype=int, default=3, doc=
"k-sigma rejection iterations for sky scale")
 
   75     skyRej = Field(dtype=float, default=3.0, doc=
"k-sigma rejection threshold for sky scale")
 
   76     background = ConfigField(dtype=BackgroundConfig, doc=
"Background measurement")
 
   77     xNumSamples = Field(dtype=int, default=4, doc=
"Number of samples in x for scaling sky frame")
 
   78     yNumSamples = Field(dtype=int, default=4, doc=
"Number of samples in y for scaling sky frame")
 
   79     stats = ConfigField(dtype=SkyStatsConfig, doc=
"Measurement of sky statistics in the samples")
 
   83     """Task for creating, persisting and using sky frames 
   85     A sky frame is like a fringe frame (the sum of many exposures of the night sky, 
   86     combined with rejection to remove astrophysical objects) except the structure 
   87     is on larger scales, and hence we bin the images and represent them as a 
   88     background model (a `lsst.afw.math.BackgroundMI`).  The sky frame represents 
   89     the dominant response of the camera to the sky background. 
   91     ConfigClass = SkyMeasurementConfig
 
   94         """Retrieve sky frame from the butler 
   98         butler : `lsst.daf.persistence.Butler` 
  101             Data identifier for calib 
  105         sky : `lsst.afw.math.BackgroundList` 
  108         exp = butler.get(
"sky", calibId)
 
  113         """Convert an exposure to background model 
  115         Calibs need to be persisted as an Exposure, so we need to convert 
  116         the persisted Exposure to a background model. 
  120         bgExp : `lsst.afw.image.Exposure` 
  121             Background model in Exposure format. 
  125         bg : `lsst.afw.math.BackgroundList` 
  128         header = bgExp.getMetadata()
 
  129         xMin = header.getScalar(
"BOX.MINX")
 
  130         yMin = header.getScalar(
"BOX.MINY")
 
  131         xMax = header.getScalar(
"BOX.MAXX")
 
  132         yMax = header.getScalar(
"BOX.MAXY")
 
  133         algorithm = header.getScalar(
"ALGORITHM")
 
  139              afwMath.ApproximateControl.UNKNOWN,
 
  143         """Convert a background model to an exposure 
  145         Calibs need to be persisted as an Exposure, so we need to convert 
  146         the background model to an Exposure. 
  150         statsImage : `lsst.afw.image.MaskedImageF` 
  151             Background model's statistics image. 
  152         bbox : `lsst.geom.Box2I` 
  153             Bounding box for image. 
  157         exp : `lsst.afw.image.Exposure` 
  158             Background model in Exposure format. 
  161         header = exp.getMetadata()
 
  162         header.set(
"BOX.MINX", bbox.getMinX())
 
  163         header.set(
"BOX.MINY", bbox.getMinY())
 
  164         header.set(
"BOX.MAXX", bbox.getMaxX())
 
  165         header.set(
"BOX.MAXY", bbox.getMaxY())
 
  166         header.set(
"ALGORITHM", self.
config.background.algorithm)
 
  170         """Measure a background model for image 
  172         This doesn't use a full-featured background model (e.g., no Chebyshev 
  173         approximation) because we just want the binning behaviour.  This will 
  174         allow us to average the bins later (`averageBackgrounds`). 
  176         The `BackgroundMI` is wrapped in a `BackgroundList` so it can be 
  177         pickled and persisted. 
  181         image : `lsst.afw.image.MaskedImage` 
  182             Image for which to measure background. 
  186         bgModel : `lsst.afw.math.BackgroundList` 
  190         stats.setAndMask(image.getMask().getPlaneBitMask(self.
config.background.mask))
 
  191         stats.setNanSafe(
True)
 
  193             self.
config.background.algorithm,
 
  194             max(int(image.getWidth()/self.
config.background.xBinSize + 0.5), 1),
 
  195             max(int(image.getHeight()/self.
config.background.yBinSize + 0.5), 1),
 
  196             "REDUCE_INTERP_ORDER",
 
  198             self.
config.background.statistic
 
  207             afwMath.ApproximateControl.UNKNOWN,
 
  212         """Average multiple background models 
  214         The input background models should be a `BackgroundList` consisting 
  215         of a single `BackgroundMI`. 
  219         bgList : `list` of `lsst.afw.math.BackgroundList` 
  220             Background models to average. 
  224         bgExp : `lsst.afw.image.Exposure` 
  225             Background model in Exposure format. 
  227         assert all(len(bg) == 1 
for bg 
in bgList), 
"Mixed bgList: %s" % ([len(bg) 
for bg 
in bgList],)
 
  228         images = [bg[0][0].getStatsImage() 
for bg 
in bgList]
 
  229         boxes = [bg[0][0].getImageBBox() 
for bg 
in bgList]
 
  230         assert len(
set((box.getMinX(), box.getMinY(), box.getMaxX(), box.getMaxY()) 
for box 
in boxes)) == 1, \
 
  231             "Bounding boxes not all equal" 
  235         maskVal = afwImage.Mask.getPlaneBitMask(
"BAD")
 
  237             bad = numpy.isnan(img.getImage().getArray())
 
  238             img.getMask().getArray()[bad] = maskVal
 
  241         stats.setAndMask(maskVal)
 
  242         stats.setNanSafe(
True)
 
  249         array = combined.getImage().getArray()
 
  250         bad = numpy.isnan(array)
 
  251         median = numpy.median(array[~bad])
 
  258         """Measure scale of background model in image 
  260         We treat the sky frame much as we would a fringe frame 
  261         (except the length scale of the variations is different): 
  262         we measure samples on the input image and the sky frame, 
  263         which we will use to determine the scaling factor in the 
  264         'solveScales` method. 
  268         image : `lsst.afw.image.Exposure` or `lsst.afw.image.MaskedImage` 
  269             Science image for which to measure scale. 
  270         skyBackground : `lsst.afw.math.BackgroundList` 
  271             Sky background model. 
  275         imageSamples : `numpy.ndarray` 
  276             Sample measurements on image. 
  277         skySamples : `numpy.ndarray` 
  278             Sample measurements on sky frame. 
  281             image = image.getMaskedImage()
 
  283         xNumSamples = 
min(self.
config.xNumSamples, image.getWidth())
 
  284         yNumSamples = 
min(self.
config.yNumSamples, image.getHeight())
 
  285         xLimits = numpy.linspace(0, image.getWidth(), xNumSamples + 1, dtype=int)
 
  286         yLimits = numpy.linspace(0, image.getHeight(), yNumSamples + 1, dtype=int)
 
  287         sky = skyBackground.getImage()
 
  288         maskVal = image.getMask().getPlaneBitMask(self.
config.stats.mask)
 
  293         for xIndex, yIndex 
in itertools.product(range(xNumSamples), range(yNumSamples)):
 
  295             xStart, xStop = xLimits[xIndex], xLimits[xIndex + 1] - 1
 
  296             yStart, yStop = yLimits[yIndex], yLimits[yIndex + 1] - 1
 
  298             subImage = image.Factory(image, box)
 
  299             subSky = sky.Factory(sky, box)
 
  302         return imageSamples, skySamples
 
  305         """Solve multiple scales for a single scale factor 
  307         Having measured samples from the image and sky frame, we 
  308         fit for the scaling factor. 
  312         scales : `list` of a `tuple` of two `numpy.ndarray` arrays 
  313             A `list` of the results from `measureScale` method. 
  322         for ii, ss 
in scales:
 
  323             imageSamples.extend(ii)
 
  324             skySamples.extend(ss)
 
  325         assert len(imageSamples) == len(skySamples)
 
  326         imageSamples = numpy.array(imageSamples)
 
  327         skySamples = numpy.array(skySamples)
 
  330             return afwMath.LeastSquares.fromDesignMatrix(skySamples[mask].reshape(mask.sum(), 1),
 
  332                                                          afwMath.LeastSquares.DIRECT_SVD).getSolution()
 
  334         mask = numpy.isfinite(imageSamples) & numpy.isfinite(skySamples)
 
  335         for ii 
in range(self.
config.skyIter):
 
  336             solution = solve(mask)
 
  337             residuals = imageSamples - solution*skySamples
 
  338             lq, uq = numpy.percentile(residuals[mask], [25, 75])
 
  339             stdev = 0.741*(uq - lq)  
 
  340             with numpy.errstate(invalid=
"ignore"):  
 
  341                 bad = numpy.abs(residuals) > self.
config.skyRej*stdev
 
  347         """Subtract sky frame from science image 
  351         image : `lsst.afw.image.Exposure` or `lsst.afw.image.MaskedImage` 
  353         skyBackground : `lsst.afw.math.BackgroundList` 
  354             Sky background model. 
  356             Scale to apply to background model. 
  357         bgList : `lsst.afw.math.BackgroundList` 
  358             List of backgrounds applied to image 
  361             image = image.getMaskedImage()
 
  363             image = image.getImage()
 
  364         image.scaledMinus(scale, skyBackground.getImage())
 
  365         if bgList 
is not None:
 
  367             bgData = 
list(skyBackground[0])
 
  369             statsImage = bg.getStatsImage().
clone()
 
  372             newBgData = [newBg] + bgData[1:]
 
  373             bgList.append(newBgData)
 
  377     """Interpolate in one dimension 
  379     Interpolates the curve provided by `xSample` and `ySample` at 
  380     the positions of `xInterp`. Automatically backs off the 
  381     interpolation method to achieve successful interpolation. 
  385     method : `lsst.afw.math.Interpolate.Style` 
  386         Interpolation method to use. 
  387     xSample : `numpy.ndarray` 
  389     ySample : `numpy.ndarray` 
  390         Vector of coordinates. 
  391     xInterp : `numpy.ndarray` 
  392         Vector of ordinates to which to interpolate. 
  396     yInterp : `numpy.ndarray` 
  397         Vector of interpolated coordinates. 
  400     if len(xSample) == 0:
 
  401         return numpy.ones_like(xInterp)*numpy.nan
 
  406         if method == afwMath.Interpolate.CONSTANT:
 
  408             return numpy.ones_like(xInterp)*numpy.nan
 
  410         if newMethod == method:
 
  411             newMethod = afwMath.Interpolate.CONSTANT
 
  416     """Interpolate bad pixels in an image array 
  418     The bad pixels are modified in the array. 
  422     array : `numpy.ndarray` 
  423         Image array with bad pixels. 
  424     isBad : `numpy.ndarray` of type `bool` 
  425         Boolean array indicating which pixels are bad. 
  426     interpolationStyle : `str` 
  427         Style for interpolation (see `lsst.afw.math.Background`); 
  428         supported values are CONSTANT, LINEAR, NATURAL_SPLINE, 
  432         raise RuntimeError(
"No good pixels in image array")
 
  433     height, width = array.shape
 
  434     xIndices = numpy.arange(width, dtype=float)
 
  435     yIndices = numpy.arange(height, dtype=float)
 
  438     for y 
in range(height):
 
  439         if numpy.any(isBad[y, :]) 
and numpy.any(isGood[y, :]):
 
  440             array[y][isBad[y]] = 
interpolate1D(method, xIndices[isGood[y]], array[y][isGood[y]],
 
  443     isBad = numpy.isnan(array)
 
  445     for x 
in range(width):
 
  446         if numpy.any(isBad[:, x]) 
and numpy.any(isGood[:, x]):
 
  447             array[:, x][isBad[:, x]] = 
interpolate1D(method, yIndices[isGood[:, x]],
 
  448                                                      array[:, x][isGood[:, x]], yIndices[isBad[:, x]])
 
  452     """Configuration for FocalPlaneBackground 
  454     Note that `xSize` and `ySize` are floating-point values, as 
  455     the focal plane frame is usually defined in units of microns 
  456     or millimetres rather than pixels. As such, their values will 
  457     need to be revised according to each particular camera. For 
  458     this reason, no defaults are set for those. 
  460     xSize = Field(dtype=float, doc=
"Bin size in x")
 
  461     ySize = Field(dtype=float, doc=
"Bin size in y")
 
  462     pixelSize = Field(dtype=float, default=1.0, doc=
"Pixel size in same units as xSize/ySize")
 
  463     minFrac = Field(dtype=float, default=0.1, doc=
"Minimum fraction of bin size for good measurement")
 
  464     mask = ListField(dtype=str, doc=
"Mask planes to treat as bad",
 
  465                      default=[
"BAD", 
"SAT", 
"INTRP", 
"DETECTED", 
"DETECTED_NEGATIVE", 
"EDGE", 
"NO_DATA"])
 
  466     interpolation = ChoiceField(
 
  467         doc=
"how to interpolate the background values. This maps to an enum; see afw::math::Background",
 
  468         dtype=str, default=
"AKIMA_SPLINE", optional=
True,
 
  470             "CONSTANT": 
"Use a single constant value",
 
  471             "LINEAR": 
"Use linear interpolation",
 
  472             "NATURAL_SPLINE": 
"cubic spline with zero second derivative at endpoints",
 
  473             "AKIMA_SPLINE": 
"higher-level nonlinear spline that is more robust to outliers",
 
  474             "NONE": 
"No background estimation is to be attempted",
 
  477     doSmooth = Field(dtype=bool, default=
False, doc=
"Do smoothing?")
 
  478     smoothScale = Field(dtype=float, default=2.0, doc=
"Smoothing scale, as a multiple of the bin size")
 
  479     binning = Field(dtype=int, default=64, doc=
"Binning to use for CCD background model (pixels)")
 
  483     """Background model for a focal plane camera 
  485     We model the background empirically with the "superpixel" method: we 
  486     measure the background in each superpixel and interpolate between 
  487     superpixels to yield the model. 
  489     The principal difference between this and `lsst.afw.math.BackgroundMI` 
  490     is that here the superpixels are defined in the frame of the focal 
  491     plane of the camera which removes discontinuities across detectors. 
  493     The constructor you probably want to use is the `fromCamera` classmethod. 
  495     There are two use patterns for building a background model: 
  497     * Serial: create a `FocalPlaneBackground`, then `addCcd` for each of the 
  500     * Parallel: create a `FocalPlaneBackground`, then `clone` it for each 
  501       of the CCDs in an exposure and use those to `addCcd` their respective 
  502       CCD image. Finally, `merge` all the clones into the original. 
  504     Once you've built the background model, you can apply it to individual 
  505     CCDs with the `toCcdBackground` method. 
  509         """Construct from a camera object 
  513         config : `FocalPlaneBackgroundConfig` 
  514             Configuration for measuring backgrounds. 
  515         camera : `lsst.afw.cameraGeom.Camera` 
  516             Camera for which to measure backgrounds. 
  520             for point 
in ccd.getCorners(afwCameraGeom.FOCAL_PLANE):
 
  521                 cameraBox.include(point)
 
  523         width, height = cameraBox.getDimensions()
 
  527         dims = 
geom.Extent2I(int(numpy.ceil(width/config.xSize)) + 2,
 
  528                              int(numpy.ceil(height/config.ySize)) + 2)
 
  530         transform = (geom.AffineTransform.makeTranslation(
geom.Extent2D(1, 1))*
 
  531                      geom.AffineTransform.makeScaling(1.0/config.xSize, 1.0/config.ySize)*
 
  532                      geom.AffineTransform.makeTranslation(offset))
 
  536     def __init__(self, config, dims, transform, values=None, numbers=None):
 
  539         Developers should note that changes to the signature of this method 
  540         require coordinated changes to the `__reduce__` and `clone` methods. 
  544         config : `FocalPlaneBackgroundConfig` 
  545             Configuration for measuring backgrounds. 
  546         dims : `lsst.geom.Extent2I` 
  547             Dimensions for background samples. 
  548         transform : `lsst.afw.geom.TransformPoint2ToPoint2` 
  549             Transformation from focal plane coordinates to sample coordinates. 
  550         values : `lsst.afw.image.ImageF` 
  551             Measured background values. 
  552         numbers : `lsst.afw.image.ImageF` 
  553             Number of pixels in each background measurement. 
  560             values = afwImage.ImageF(self.
dims)
 
  563             values = values.clone()
 
  564         assert(values.getDimensions() == self.
dims)
 
  567             numbers = afwImage.ImageF(self.
dims)  
 
  570             numbers = numbers.clone()
 
  571         assert(numbers.getDimensions() == self.
dims)
 
  583         We measure the background on the CCD (clipped mean), and record 
  584         the results in the model.  For simplicity, measurements are made 
  585         in a box on the CCD corresponding to the warped coordinates of the 
  586         superpixel rather than accounting for little rotations, etc. 
  587         We also record the number of pixels used in the measurement so we 
  588         can have a measure of confidence in each bin's value. 
  592         exposure : `lsst.afw.image.Exposure` 
  593             CCD exposure to measure 
  595         detector = exposure.getDetector()
 
  596         transform = detector.getTransformMap().getTransform(detector.makeCameraSys(afwCameraGeom.PIXELS),
 
  597                                                             detector.makeCameraSys(afwCameraGeom.FOCAL_PLANE))
 
  598         image = exposure.getMaskedImage()
 
  599         maskVal = image.getMask().getPlaneBitMask(self.
config.mask)
 
  602         toSample = transform.then(self.
transform)  
 
  604         warped = afwImage.ImageF(self.
_values.getBBox())
 
  605         warpedCounts = afwImage.ImageF(self.
_numbers.getBBox())
 
  606         width, height = warped.getDimensions()
 
  609         stats.setAndMask(maskVal)
 
  610         stats.setNanSafe(
True)
 
  612         pixels = itertools.product(range(width), range(height))
 
  613         for xx, yy 
in pixels:
 
  614             llc = toSample.applyInverse(
geom.Point2D(xx - 0.5, yy - 0.5))
 
  615             urc = toSample.applyInverse(
geom.Point2D(xx + 0.5, yy + 0.5))
 
  617             bbox.clip(image.getBBox())
 
  620             subImage = image.Factory(image, bbox)
 
  622             mean = result.getValue(afwMath.MEANCLIP)
 
  623             num = result.getValue(afwMath.NPOINT)
 
  624             if not numpy.isfinite(mean) 
or not numpy.isfinite(num):
 
  626             warped[xx, yy, afwImage.LOCAL] = mean*num
 
  627             warpedCounts[xx, yy, afwImage.LOCAL] = num
 
  633         """Produce a background model for a CCD 
  635         The superpixel background model is warped back to the 
  636         CCD frame, for application to the individual CCD. 
  640         detector : `lsst.afw.cameraGeom.Detector` 
  641             CCD for which to produce background model. 
  642         bbox : `lsst.geom.Box2I` 
  643             Bounding box of CCD exposure. 
  647         bg : `lsst.afw.math.BackgroundList` 
  648             Background model for CCD. 
  650         transform = detector.getTransformMap().getTransform(detector.makeCameraSys(afwCameraGeom.PIXELS),
 
  651                                                             detector.makeCameraSys(afwCameraGeom.FOCAL_PLANE))
 
  652         binTransform = (geom.AffineTransform.makeScaling(self.
config.binning)*
 
  653                         geom.AffineTransform.makeTranslation(
geom.Extent2D(0.5, 0.5)))
 
  659         fpNorm = afwImage.ImageF(focalPlane.getBBox())
 
  662         image = afwImage.ImageF(bbox.getDimensions()//self.
config.binning)
 
  663         norm = afwImage.ImageF(image.getBBox())
 
  670         isBad = numpy.isnan(image.getArray())
 
  671         mask.getArray()[isBad] = mask.getPlaneBitMask(
"BAD")
 
  672         image.getArray()[isBad] = image.getArray()[~isBad].mean()
 
  678              afwMath.ApproximateControl.UNKNOWN,
 
  683         """Merge with another FocalPlaneBackground 
  685         This allows multiple background models to be constructed from 
  686         different CCDs, and then merged to form a single consistent 
  687         background model for the entire focal plane. 
  691         other : `FocalPlaneBackground` 
  692             Another background model to merge. 
  696         self : `FocalPlaneBackground` 
  697             The merged background model. 
  699         if (self.
config.xSize, self.
config.ySize) != (other.config.xSize, other.config.ySize):
 
  700             raise RuntimeError(
"Size mismatch: %s vs %s" % ((self.
config.xSize, self.
config.ySize),
 
  701                                                             (other.config.xSize, other.config.ySize)))
 
  702         if self.
dims != other.dims:
 
  703             raise RuntimeError(
"Dimensions mismatch: %s vs %s" % (self.
dims, other.dims))
 
  709         """Merge with another FocalPlaneBackground 
  713         other : `FocalPlaneBackground` 
  714             Another background model to merge. 
  718         self : `FocalPlaneBackground` 
  719             The merged background model. 
  721         return self.
merge(other)
 
  724         """Return the background model data 
  726         This is the measurement of the background for each of the superpixels. 
  730         thresh = (self.
config.minFrac*
 
  732         isBad = self.
_numbers.getArray() < thresh
 
  734             array = values.getArray()
 
  736             isBad = numpy.isnan(values.array)
 
  743     """Configuration for MaskObjectsTask""" 
  744     nIter = Field(dtype=int, default=3, doc=
"Number of iterations")
 
  745     subtractBackground = ConfigurableField(target=measAlg.SubtractBackgroundTask,
 
  746                                            doc=
"Background subtraction")
 
  747     detection = ConfigurableField(target=measAlg.SourceDetectionTask, doc=
"Source detection")
 
  748     detectSigma = Field(dtype=float, default=5.0, doc=
"Detection threshold (standard deviations)")
 
  749     doInterpolate = Field(dtype=bool, default=
True, doc=
"Interpolate when removing objects?")
 
  750     interpolate = ConfigurableField(target=measAlg.SubtractBackgroundTask, doc=
"Interpolation")
 
  753         self.
detection.reEstimateBackground = 
False 
  754         self.
detection.doTempLocalBackground = 
False 
  755         self.
detection.doTempWideBackground = 
False 
  763         if (self.
detection.reEstimateBackground 
or 
  766             raise RuntimeError(
"Incorrect settings for object masking: reEstimateBackground, " 
  767                                "doTempLocalBackground and doTempWideBackground must be False")
 
  771     """Iterative masking of objects on an Exposure 
  773     This task makes more exhaustive object mask by iteratively doing detection 
  774     and background-subtraction. The purpose of this task is to get true 
  775     background removing faint tails of large objects. This is useful to get a 
  776     clean sky estimate from relatively small number of visits. 
  778     We deliberately use the specified ``detectSigma`` instead of the PSF, 
  779     in order to better pick up the faint wings of objects. 
  781     ConfigClass = MaskObjectsConfig
 
  790     def run(self, exposure, maskPlanes=None):
 
  791         """Mask objects on Exposure 
  793         Objects are found and removed. 
  797         exposure : `lsst.afw.image.Exposure` 
  798             Exposure on which to mask objects. 
  799         maskPlanes : iterable of `str`, optional 
  800             List of mask planes to remove. 
  806         """Iteratively find objects on an exposure 
  808         Objects are masked with the ``DETECTED`` mask plane. 
  812         exposure : `lsst.afw.image.Exposure` 
  813             Exposure on which to mask objects. 
  815         for _ 
in range(self.
config.nIter):
 
  816             bg = self.subtractBackground.
run(exposure).background
 
  817             self.detection.detectFootprints(exposure, sigma=self.
config.detectSigma, clearMask=
True)
 
  818             exposure.maskedImage += bg.getImage()
 
  821         """Remove objects from exposure 
  823         We interpolate over using a background model if ``doInterpolate`` is 
  824         set; otherwise we simply replace everything with the median. 
  828         exposure : `lsst.afw.image.Exposure` 
  829             Exposure on which to mask objects. 
  830         maskPlanes : iterable of `str`, optional 
  831             List of mask planes to remove. ``DETECTED`` will be added as well. 
  833         image = exposure.image
 
  835         maskVal = mask.getPlaneBitMask(
"DETECTED")
 
  836         if maskPlanes 
is not None:
 
  837             maskVal |= mask.getPlaneBitMask(maskPlanes)
 
  838         isBad = mask.array & maskVal > 0
 
  840         if self.
config.doInterpolate:
 
  841             smooth = self.interpolate.fitBackground(exposure.maskedImage)
 
  842             replace = smooth.getImageF().array[isBad]
 
  843             mask.array &= ~mask.getPlaneBitMask([
"DETECTED"])
 
  845             replace = numpy.median(image.array[~isBad])
 
  846         image.array[isBad] = replace
 
  850     """Gaussian-smooth an array while ignoring bad pixels 
  852     It's not sufficient to set the bad pixels to zero, as then they're treated 
  853     as if they are zero, rather than being ignored altogether. We need to apply 
  854     a correction to that image that removes the effect of the bad pixels. 
  858     array : `numpy.ndarray` of floating-point 
  860     bad : `numpy.ndarray` of `bool` 
  861         Flag array indicating bad pixels. 
  867     convolved : `numpy.ndarray` 
  870     convolved = gaussian_filter(numpy.where(bad, 0.0, array), sigma, mode=
"constant", cval=0.0)
 
  871     denominator = gaussian_filter(numpy.where(bad, 0.0, 1.0), sigma, mode=
"constant", cval=0.0)
 
  872     return convolved/denominator