lsst.pipe.tasks g4544ed029c+0a6c2fd989
Loading...
Searching...
No Matches
Classes | Variables
lsst.pipe.tasks.subtractBrightStars Namespace Reference

Classes

class  SubtractBrightStarsConnections
 

Variables

 logger = logging.getLogger(__name__)
 
 inputExposure : `~lsst.afw.image.ExposureF`
 
 inputBrightStarStamps :
 
 inputExtendedPsf : `~lsst.pipe.tasks.extended_psf.ExtendedPsf`
 
 dataId : `dict` or `~lsst.daf.butler.DataCoordinate`
 
 skyCorr : `~lsst.afw.math.backgroundList.BackgroundList`, optional
 
 refObjLoader : `~lsst.meas.algorithms.ReferenceObjectLoader`, optional
 
 subtractorExp : `~lsst.afw.image.ExposureF`
 
 invImages : `list` [`~lsst.afw.image.MaskedImageF`]
 
 calexp : `~lsst.afw.image.Exposure` or `~lsst.afw.image.MaskedImage`
 
 model : `~lsst.afw.image.MaskedImageF`
 
 star : `~lsst.meas.algorithms.brightStarStamps.BrightStarStamp`
 
 inPlace : `bool`
 
 nb90Rots : `int`
 
 psf_annular_flux : `float`, optional
 
 scalingFactor : `float`
 
 brightStarList :
 
 annulusImage : `~lsst.afw.image.MaskedImageF`
 
 brightStarStamp :
 
 annulus : `~lsst.afw.image.MaskedImageF`
 
float annularFlux (between 0 and 1)
 
 maskedModel : `~lsst.afw.image.MaskedImageF`
 
 brightStarStamps :
 
numpy PsfAnnularFluxes .array
 
 bbox : `~lsst.geom.Box2I`
 
 invImage : `~lsst.afw.image.MaskedImageF`
 
 subtractor : `~lsst.afw.image.MaskedImageF`
 
bool multipleAnnuli , optional
 

Detailed Description

Retrieve extended PSF model and subtract bright stars at visit level.

Variable Documentation

◆ annularFlux

float lsst.pipe.tasks.subtractBrightStars.annularFlux (between 0 and 1)

Definition at line 587 of file subtractBrightStars.py.

◆ annulus

lsst.pipe.tasks.subtractBrightStars.annulus : `~lsst.afw.image.MaskedImageF`

Definition at line 564 of file subtractBrightStars.py.

◆ annulusImage

lsst.pipe.tasks.subtractBrightStars.annulusImage : `~lsst.afw.image.MaskedImageF`
self.setWarpTask()
missedStars = self.warper.extractStamps(
    inputExposure, refObjLoader=refObjLoader, inputBrightStarStamps=inputBrightStarStamps
)
if missedStars.starStamps:
    self.warpOutputs = self.warper.warpStamps(missedStars.starStamps, missedStars.pixCenters)
    brightStarList = [
        BrightStarStamp(
            stamp_im=warp,
            archive_element=transform,
            position=self.warpOutputs.xy0s[j],
            gaiaGMag=missedStars.gMags[j],
            gaiaId=missedStars.gaiaIds[j],
            minValidAnnulusFraction=self.warper.config.minValidAnnulusFraction,
        )
        for j, (warp, transform) in enumerate(
            zip(self.warpOutputs.warpedStars, self.warpOutputs.warpTransforms)
        )
    ]
else:
    brightStarList = []
return brightStarList

def initAnnulusImage(self):
# Create SpanSet of annulus.
outerCircle = SpanSet.fromShape(
    brightStarStamp.optimalOuterRadius, Stencil.CIRCLE, offset=self.warper.modelCenter
)
innerCircle = SpanSet.fromShape(
    brightStarStamp.optimalInnerRadius, Stencil.CIRCLE, offset=self.warper.modelCenter
)
annulus = outerCircle.intersectNot(innerCircle)
return annulus

def applyStatsControl(self, annulusImage):

Definition at line 538 of file subtractBrightStars.py.

◆ bbox

lsst.pipe.tasks.subtractBrightStars.bbox : `~lsst.geom.Box2I`

Definition at line 675 of file subtractBrightStars.py.

◆ brightStarList

lsst.pipe.tasks.subtractBrightStars.brightStarList :

Definition at line 505 of file subtractBrightStars.py.

◆ brightStarStamp

lsst.pipe.tasks.subtractBrightStars.brightStarStamp :
maskPlaneDict = self.model.mask.getMaskPlaneDict()
annulusImage = MaskedImageF(self.modelStampSize, planeDict=maskPlaneDict)
annulusImage.mask.array[:] = 2 ** maskPlaneDict["NO_DATA"]
return annulusImage

def createAnnulus(self, brightStarStamp):
andMask = reduce(
    ior, (annulusImage.mask.getPlaneBitMask(bm) for bm in self.warper.config.badMaskPlanes)
)
self.missedStatsControl.setAndMask(andMask)
annulusStat = makeStatistics(annulusImage, self.missedStatsFlag, self.missedStatsControl)
return annulusStat.getValue()

def findPsfAnnularFlux(self, brightStarStamp, maskedModel):
outerRadii = []
annularFluxes = []
maskedModel = MaskedImageF(self.model.image)
# The model has wrong bbox values. Should be fixed in extended_psf.py?
maskedModel.setXY0(0, 0)
for star in brightStarStamps:
    if star.optimalOuterRadius not in outerRadii:
        annularFlux = self.findPsfAnnularFlux(star, maskedModel)
        outerRadii.append(star.optimalOuterRadius)
        annularFluxes.append(annularFlux)
return np.array([outerRadii, annularFluxes]).T

def preparePlaneModelStamp(self, brightStarStamp):

Definition at line 558 of file subtractBrightStars.py.

◆ brightStarStamps

lsst.pipe.tasks.subtractBrightStars.brightStarStamps :
annulusImage = self.initAnnulusImage()
annulus = self.createAnnulus(brightStarStamp)
annulus.copyMaskedImage(maskedModel, annulusImage)
annularFlux = self.applyStatsControl(annulusImage)
return annularFlux

def findPsfAnnularFluxes(self, brightStarStamps):

Definition at line 630 of file subtractBrightStars.py.

◆ calexp

lsst.pipe.tasks.subtractBrightStars.calexp : `~lsst.afw.image.Exposure` or `~lsst.afw.image.MaskedImage`
self.inputExpBBox = inputExposure.getBBox()
if self.config.doApplySkyCorr and (skyCorr is not None):
    self.log.info(
        "Applying sky correction to exposure %s (exposure will be modified in-place).", dataId
    )
    self.applySkyCorr(inputExposure, skyCorr)

# Create an empty image the size of the exposure.
# TODO: DM-31085 (set mask planes).
subtractorExp = ExposureF(bbox=inputExposure.getBBox())
subtractor = subtractorExp.maskedImage

# Make a copy of the input model.
self.model = inputExtendedPsf(dataId["detector"]).clone()
self.modelStampSize = self.model.getDimensions()
# Number of 90 deg. rotations to reverse each stamp's rotation.
self.inv90Rots = 4 - inputBrightStarStamps.nb90Rots % 4
self.model = rotateImageBy90(self.model, self.inv90Rots)

brightStarList = self.makeBrightStarList(inputBrightStarStamps, inputExposure, refObjLoader)
invImages = []
subtractor, invImages = self.buildSubtractor(
    inputBrightStarStamps, subtractor, invImages, multipleAnnuli=False
)
if brightStarList:
    self.setMissedStarsStatsControl()
    # This may change when multiple star bins are used for PSF
    # creation.
    innerRadius = inputBrightStarStamps._innerRadius
    outerRadius = inputBrightStarStamps._outerRadius
    brightStarStamps, badStamps = BrightStarStamps.initAndNormalize(
        brightStarList,
        innerRadius=innerRadius,
        outerRadius=outerRadius,
        nb90Rots=self.warpOutputs.nb90Rots,
        imCenter=self.warper.modelCenter,
        use_archive=True,
        statsControl=self.missedStatsControl,
        statsFlag=self.missedStatsFlag,
        badMaskPlanes=self.warper.config.badMaskPlanes,
        discardNanFluxObjects=False,
        forceFindFlux=True,
    )

    self.psf_annular_fluxes = self.findPsfAnnularFluxes(brightStarStamps)
    subtractor, invImages = self.buildSubtractor(
        brightStarStamps, subtractor, invImages, multipleAnnuli=True
    )
else:
    badStamps = []
badStamps = BrightStarStamps(badStamps)

return subtractorExp, invImages, badStamps

def _setUpStatistics(self, exampleMask):
if self.config.scalingType == "leastSquare":
    # Set the mask planes which will be ignored.
    andMask = reduce(
        ior,
        (exampleMask.getPlaneBitMask(bm) for bm in self.config.badMaskPlanes),
    )
    self.statsControl = StatisticsControl(
        andMask=andMask,
    )
    self.statsFlag = stringToStatisticsProperty("SUM")

def applySkyCorr(self, calexp, skyCorr):

Definition at line 391 of file subtractBrightStars.py.

◆ dataId

lsst.pipe.tasks.subtractBrightStars.dataId : `dict` or `~lsst.daf.butler.DataCoordinate`

Definition at line 293 of file subtractBrightStars.py.

◆ inPlace

lsst.pipe.tasks.subtractBrightStars.inPlace : `bool`

Definition at line 412 of file subtractBrightStars.py.

◆ inputBrightStarStamps

lsst.pipe.tasks.subtractBrightStars.inputBrightStarStamps :
if self.config.scalingType == "annularFlux":
    scalingFactor = star.annularFlux * psf_annular_flux
elif self.config.scalingType == "leastSquare":
    if self.statsControl is None:
        self._setUpStatistics(star.stamp_im.mask)
    starIm = star.stamp_im.clone()
    # Rotate the star postage stamp.
    starIm = rotateImageBy90(starIm, nb90Rots)
    # Reverse the prior star flux normalization ("unnormalize").
    starIm *= star.annularFlux
    # The estimator of the scalingFactor (f) that minimizes (Y-fX)^2
    # is E[XY]/E[XX].
    xy = starIm.clone()
    xy.image.array *= model.image.array
    xx = starIm.clone()
    xx.image.array = model.image.array**2
    # Compute the least squares scaling factor.
    xySum = makeStatistics(xy, self.statsFlag, self.statsControl).getValue()
    xxSum = makeStatistics(xx, self.statsFlag, self.statsControl).getValue()
    scalingFactor = xySum / xxSum if xxSum else 1
if inPlace:
    model.image *= scalingFactor
return scalingFactor

def _overrideWarperConfig(self):
# TODO: Replace these copied values with a warperConfig.
self.warper.config.minValidAnnulusFraction = self.config.minValidAnnulusFraction
self.warper.config.numSigmaClip = self.config.numSigmaClip
self.warper.config.numIter = self.config.numIter
self.warper.config.annularFluxStatistic = self.config.annularFluxStatistic
self.warper.config.badMaskPlanes = self.config.badMaskPlanes
self.warper.config.stampSize = self.config.subtractionBox
self.warper.modelStampBuffer = self.config.subtractionBoxBuffer
self.warper.config.magLimit = self.config.magLimit
self.warper.setModelStamp()

def setMissedStarsStatsControl(self):
self.missedStatsControl = StatisticsControl(
    numSigmaClip=self.warper.config.numSigmaClip,
    numIter=self.warper.config.numIter,
)
self.missedStatsFlag = stringToStatisticsProperty(self.warper.config.annularFluxStatistic)

def setWarpTask(self):
self.warper = ProcessBrightStarsTask()
self._overrideWarperConfig()
self.warper.modelCenter = self.modelStampSize[0] // 2, self.modelStampSize[1] // 2

def makeBrightStarList(self, inputBrightStarStamps, inputExposure, refObjLoader):

Definition at line 285 of file subtractBrightStars.py.

◆ inputExposure

lsst.pipe.tasks.subtractBrightStars.inputExposure : `~lsst.afw.image.ExposureF`
    doWriteSubtractor = Field[bool](
        doc="Should an exposure containing all bright star models be written to disk?",
        default=True,
    )
    doWriteSubtractedExposure = Field[bool](
        doc="Should an exposure with bright stars subtracted be written to disk?",
        default=True,
    )
    magLimit = Field[float](
        doc="Magnitude limit, in Gaia G; all stars brighter than this value will be subtracted",
        default=18,
    )
    minValidAnnulusFraction = Field[float](
        doc="Minimum number of valid pixels that must fall within the annulus for the bright star to be "
        "saved for subsequent generation of a PSF.",
        default=0.0,
    )
    numSigmaClip = Field[float](
        doc="Sigma for outlier rejection; ignored if annularFluxStatistic != 'MEANCLIP'.",
        default=4,
    )
    numIter = Field[int](
        doc="Number of iterations of outlier rejection; ignored if annularFluxStatistic != 'MEANCLIP'.",
        default=3,
    )
    warpingKernelName = ChoiceField[str](
        doc="Warping kernel",
        default="lanczos5",
        allowed={
            "bilinear": "bilinear interpolation",
            "lanczos3": "Lanczos kernel of order 3",
            "lanczos4": "Lanczos kernel of order 4",
            "lanczos5": "Lanczos kernel of order 5",
            "lanczos6": "Lanczos kernel of order 6",
            "lanczos7": "Lanczos kernel of order 7",
        },
    )
    scalingType = ChoiceField[str](
        doc="How the model should be scaled to each bright star; implemented options are "
        "`annularFlux` to reuse the annular flux of each stamp, or `leastSquare` to perform "
        "least square fitting on each pixel with no bad mask plane set.",
        default="leastSquare",
        allowed={
            "annularFlux": "reuse BrightStarStamp annular flux measurement",
            "leastSquare": "find least square scaling factor",
        },
    )
    annularFluxStatistic = ChoiceField[str](
        doc="Type of statistic to use to compute annular flux.",
        default="MEANCLIP",
        allowed={
            "MEAN": "mean",
            "MEDIAN": "median",
            "MEANCLIP": "clipped mean",
        },
    )
    badMaskPlanes = ListField[str](
        doc="Mask planes that, if set, lead to associated pixels not being included in the computation of "
        "the scaling factor (`BAD` should always be included). Ignored if scalingType is `annularFlux`, "
        "as the stamps are expected to already be normalized.",
        # Note that `BAD` should always be included, as secondary detected
        # sources (i.e., detected sources other than the primary source of
        # interest) also get set to `BAD`.
        default=("BAD", "CR", "CROSSTALK", "EDGE", "NO_DATA", "SAT", "SUSPECT", "UNMASKEDNAN"),
    )
    subtractionBox = ListField[int](
        doc="Size of the stamps to be extracted, in pixels.",
        default=(250, 250),
    )
    subtractionBoxBuffer = Field[float](
        doc=(
            "'Buffer' (multiplicative) factor to be applied to determine the size of the stamp the "
            "processed stars will be saved in. This is also the size of the extended PSF model. The buffer "
            "region is masked and contain no data and subtractionBox determines the region where contains "
            "the data."
        ),
        default=1.1,
    )
    doApplySkyCorr = Field[bool](
        doc="Apply full focal plane sky correction before extracting stars?",
        default=True,
    )
    refObjLoader = ConfigField[LoadReferenceObjectsConfig](
        doc="Reference object loader for astrometric calibration.",
    )


class SubtractBrightStarsTask(PipelineTask):
ConfigClass = SubtractBrightStarsConfig
_DefaultName = "subtractBrightStars"

def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)
    # Placeholders to set up Statistics if scalingType is leastSquare.
    self.statsControl, self.statsFlag = None, None
    # Warping control; only contains shiftingALg provided in config.
    self.warpControl = WarpingControl(self.config.warpingKernelName)

def runQuantum(self, butlerQC, inputRefs, outputRefs):
    # Docstring inherited.
    inputs = butlerQC.get(inputRefs)
    dataId = butlerQC.quantum.dataId
    refObjLoader = ReferenceObjectLoader(
        dataIds=[ref.datasetRef.dataId for ref in inputRefs.refCat],
        refCats=inputs.pop("refCat"),
        name=self.config.connections.refCat,
        config=self.config.refObjLoader,
    )
    subtractor, _, badStamps = self.run(**inputs, dataId=dataId, refObjLoader=refObjLoader)
    if self.config.doWriteSubtractedExposure:
        outputExposure = inputs["inputExposure"].clone()
        outputExposure.image -= subtractor.image
    else:
        outputExposure = None
    outputBackgroundExposure = subtractor if self.config.doWriteSubtractor else None
    # In its current state, the code produces outputBadStamps which are the
    # stamps of stars that have not been subtracted from the image for any
    # reason. If all the stars are subtracted from the calexp, the output
    # is an empty fits file.
    output = Struct(
        outputExposure=outputExposure,
        outputBackgroundExposure=outputBackgroundExposure,
        outputBadStamps=badStamps,
    )
    butlerQC.put(output, outputRefs)

def run(
    self, inputExposure, inputBrightStarStamps, inputExtendedPsf, dataId, skyCorr=None, refObjLoader=None
):

Definition at line 283 of file subtractBrightStars.py.

◆ inputExtendedPsf

lsst.pipe.tasks.subtractBrightStars.inputExtendedPsf : `~lsst.pipe.tasks.extended_psf.ExtendedPsf`

Definition at line 290 of file subtractBrightStars.py.

◆ invImage

lsst.pipe.tasks.subtractBrightStars.invImage : `~lsst.afw.image.MaskedImageF`

Definition at line 679 of file subtractBrightStars.py.

◆ invImages

lsst.pipe.tasks.subtractBrightStars.invImages : `list` [`~lsst.afw.image.MaskedImageF`]

Definition at line 309 of file subtractBrightStars.py.

◆ logger

lsst.pipe.tasks.subtractBrightStars.logger = logging.getLogger(__name__)

Definition at line 49 of file subtractBrightStars.py.

◆ maskedModel

lsst.pipe.tasks.subtractBrightStars.maskedModel : `~lsst.afw.image.MaskedImageF`

Definition at line 610 of file subtractBrightStars.py.

◆ model

lsst.pipe.tasks.subtractBrightStars.model : `~lsst.afw.image.MaskedImageF`
if isinstance(calexp, Exposure):
    calexp = calexp.getMaskedImage()
calexp -= skyCorr.getImage()

def scaleModel(self, model, star, inPlace=True, nb90Rots=0, psf_annular_flux=1.0):

Definition at line 407 of file subtractBrightStars.py.

◆ multipleAnnuli

bool lsst.pipe.tasks.subtractBrightStars.multipleAnnuli , optional

Definition at line 725 of file subtractBrightStars.py.

◆ nb90Rots

lsst.pipe.tasks.subtractBrightStars.nb90Rots : `int`

Definition at line 414 of file subtractBrightStars.py.

◆ psf_annular_flux

lsst.pipe.tasks.subtractBrightStars.psf_annular_flux : `float`, optional

Definition at line 416 of file subtractBrightStars.py.

◆ PsfAnnularFluxes

numpy lsst.pipe.tasks.subtractBrightStars.PsfAnnularFluxes .array

Definition at line 636 of file subtractBrightStars.py.

◆ refObjLoader

lsst.pipe.tasks.subtractBrightStars.refObjLoader : `~lsst.meas.algorithms.ReferenceObjectLoader`, optional

Definition at line 300 of file subtractBrightStars.py.

◆ scalingFactor

lsst.pipe.tasks.subtractBrightStars.scalingFactor : `float`

Definition at line 424 of file subtractBrightStars.py.

◆ skyCorr

lsst.pipe.tasks.subtractBrightStars.skyCorr : `~lsst.afw.math.backgroundList.BackgroundList`, optional

Definition at line 296 of file subtractBrightStars.py.

◆ star

lsst.pipe.tasks.subtractBrightStars.star : `~lsst.meas.algorithms.brightStarStamps.BrightStarStamp`

Definition at line 410 of file subtractBrightStars.py.

◆ subtractor

lsst.pipe.tasks.subtractBrightStars.subtractor : `~lsst.afw.image.MaskedImageF`
# Set the origin.
self.model.setXY0(brightStarStamp.position)
# Create an empty destination image.
invTransform = brightStarStamp.archive_element.inverted()
invOrigin = Point2I(invTransform.applyForward(Point2D(brightStarStamp.position)))
bbox = Box2I(corner=invOrigin, dimensions=self.modelStampSize)
invImage = MaskedImageF(bbox)
# Apply inverse transform.
goodPix = warpImage(invImage, self.model, invTransform, self.warpControl)
if not goodPix:
    # Do we want to find another way or just subtract the non-warped
    # scaled model?
    # Currently the code just leaves the failed ones un-subtracted.
    raise RuntimeError(
        f"Warping of a model failed for star {brightStarStamp.gaiaId}: no good pixel in output."
    )
return bbox, invImage

def addScaledModel(self, subtractor, brightStarStamp, multipleAnnuli=False):

Definition at line 718 of file subtractBrightStars.py.

◆ subtractorExp

lsst.pipe.tasks.subtractBrightStars.subtractorExp : `~lsst.afw.image.ExposureF`

Definition at line 305 of file subtractBrightStars.py.