22 import astropy.units 
as u
 
   24 from scipy.spatial 
import cKDTree
 
   26 from lsst.geom import Box2D, radians, SpherePoint
 
   28 from lsst.pipe.base import PipelineTask, PipelineTaskConnections, Struct
 
   29 import lsst.pipe.base.connectionTypes 
as connTypes
 
   32 __all__ = [
"MatchFakesTask",
 
   34            "MatchVariableFakesConfig",
 
   35            "MatchVariableFakesTask"]
 
   39                             defaultTemplates={
"coaddName": 
"deep",
 
   40                                               "fakesType": 
"fakes_"},
 
   46     fakeCat = connTypes.Input(
 
   47         doc=
"Catalog of fake sources inserted into an image.",
 
   48         name=
"{fakesType}fakeSourceCat",
 
   49         storageClass=
"DataFrame",
 
   50         dimensions=(
"tract", 
"skymap")
 
   52     diffIm = connTypes.Input(
 
   53         doc=
"Difference image on which the DiaSources were detected.",
 
   54         name=
"{fakesType}{coaddName}Diff_differenceExp",
 
   55         storageClass=
"ExposureF",
 
   56         dimensions=(
"instrument", 
"visit", 
"detector"),
 
   58     associatedDiaSources = connTypes.Input(
 
   59         doc=
"A DiaSource catalog to match against fakeCat. Assumed " 
   61         name=
"{fakesType}{coaddName}Diff_assocDiaSrc",
 
   62         storageClass=
"DataFrame",
 
   63         dimensions=(
"instrument", 
"visit", 
"detector"),
 
   65     matchedDiaSources = connTypes.Output(
 
   66         doc=
"A catalog of those fakeCat sources that have a match in " 
   67             "associatedDiaSources. The schema is the union of the schemas for " 
   68             "``fakeCat`` and ``associatedDiaSources``.",
 
   69         name=
"{fakesType}{coaddName}Diff_matchDiaSrc",
 
   70         storageClass=
"DataFrame",
 
   71         dimensions=(
"instrument", 
"visit", 
"detector"),
 
   75 class MatchFakesConfig(
 
   77         pipelineConnections=MatchFakesConnections):
 
   78     """Config for MatchFakesTask. 
   80     matchDistanceArcseconds = pexConfig.RangeField(
 
   81         doc=
"Distance in arcseconds to match within.",
 
   89 class MatchFakesTask(PipelineTask):
 
   90     """Match a pre-existing catalog of fakes to a catalog of detections on 
   93     This task is generally for injected sources that cannot be easily 
   94     identified by their footprints such as in the case of detector sources 
   95     post image differencing. 
   98     _DefaultName = 
"matchFakes" 
   99     ConfigClass = MatchFakesConfig
 
  101     def runQuantum(self, butlerQC, inputRefs, outputRefs):
 
  102         inputs = butlerQC.get(inputRefs)
 
  104         outputs = self.run(**inputs)
 
  105         butlerQC.put(outputs, outputRefs)
 
  107     def run(self, fakeCat, diffIm, associatedDiaSources):
 
  108         """Match fakes to detected diaSources within a difference image bound. 
  112         fakeCat : `pandas.DataFrame` 
  113             Catalog of fakes to match to detected diaSources. 
  114         diffIm : `lsst.afw.image.Exposure` 
  115             Difference image where ``associatedDiaSources`` were detected. 
  116         associatedDiaSources : `pandas.DataFrame` 
  117             Catalog of difference image sources detected in ``diffIm``. 
  121         result : `lsst.pipe.base.Struct` 
  122             Results struct with components. 
  124             - ``matchedDiaSources`` : Fakes matched to input diaSources. Has 
  125               length of ``fakeCat``. (`pandas.DataFrame`) 
  127         trimmedFakes = self._trimFakeCat(fakeCat, diffIm)
 
  128         nPossibleFakes = len(trimmedFakes)
 
  130         fakeVects = self._getVectors(trimmedFakes[self.config.raColName],
 
  131                                      trimmedFakes[self.config.decColName])
 
  132         diaSrcVects = self._getVectors(
 
  133             np.radians(associatedDiaSources.loc[:, 
"ra"]),
 
  134             np.radians(associatedDiaSources.loc[:, 
"decl"]))
 
  136         diaSrcTree = cKDTree(diaSrcVects)
 
  137         dist, idxs = diaSrcTree.query(
 
  139             distance_upper_bound=np.radians(self.config.matchDistanceArcseconds / 3600))
 
  140         nFakesFound = np.isfinite(dist).sum()
 
  142         self.log.
info(
"Found %d out of %d possible.", nFakesFound, nPossibleFakes)
 
  143         diaSrcIds = associatedDiaSources.iloc[np.where(np.isfinite(dist), idxs, 0)][
"diaSourceId"].to_numpy()
 
  144         matchedFakes = trimmedFakes.assign(diaSourceId=np.where(np.isfinite(dist), diaSrcIds, 0))
 
  147             matchedDiaSources=matchedFakes.merge(
 
  148                 associatedDiaSources.reset_index(drop=
True), on=
"diaSourceId", how=
"left")
 
  151     def _trimFakeCat(self, fakeCat, image):
 
  152         """Trim the fake cat to about the size of the input image. 
  156         fakeCat : `pandas.core.frame.DataFrame` 
  157             The catalog of fake sources to be input 
  158         image : `lsst.afw.image.exposure.exposure.ExposureF` 
  159             The image into which the fake sources should be added 
  163         fakeCat : `pandas.core.frame.DataFrame` 
  164             The original fakeCat trimmed to the area of the image 
  168         bbox = 
Box2D(image.getBBox())
 
  172                                 row[self.config.decColName],
 
  174             cent = wcs.skyToPixel(coord)
 
  175             return bbox.contains(cent)
 
  177         return fakeCat[fakeCat.apply(trim, axis=1)]
 
  179     def _getVectors(self, ras, decs):
 
  180         """Convert ra dec to unit vectors on the sphere. 
  184         ras : `numpy.ndarray`, (N,) 
  185             RA coordinates in radians. 
  186         decs : `numpy.ndarray`, (N,) 
  187             Dec coordinates in radians. 
  191         vectors : `numpy.ndarray`, (N, 3) 
  192             Vectors on the unit sphere for the given RA/DEC values. 
  194         vectors = np.empty((len(ras), 3))
 
  196         vectors[:, 2] = np.sin(decs)
 
  197         vectors[:, 0] = np.cos(decs) * np.cos(ras)
 
  198         vectors[:, 1] = np.cos(decs) * np.sin(ras)
 
  204     ccdVisitFakeMagnitudes = connTypes.Input(
 
  205         doc=
"Catalog of fakes with magnitudes scattered for this ccdVisit.",
 
  206         name=
"{fakesType}ccdVisitFakeMagnitudes",
 
  207         storageClass=
"DataFrame",
 
  208         dimensions=(
"instrument", 
"visit", 
"detector"),
 
  212 class MatchVariableFakesConfig(MatchFakesConfig,
 
  213                                pipelineConnections=MatchVariableFakesConnections):
 
  214     """Config for MatchFakesTask. 
  219 class MatchVariableFakesTask(MatchFakesTask):
 
  220     """Match injected fakes to their detected sources in the catalog and 
  221     compute their expected brightness in a difference image assuming perfect 
  224     This task is generally for injected sources that cannot be easily 
  225     identified by their footprints such as in the case of detector sources 
  226     post image differencing. 
  228     _DefaultName = 
"matchVariableFakes" 
  229     ConfigClass = MatchVariableFakesConfig
 
  231     def runQuantum(self, butlerQC, inputRefs, outputRefs):
 
  232         inputs = butlerQC.get(inputRefs)
 
  233         inputs[
"band"] = butlerQC.quantum.dataId[
"band"]
 
  235         outputs = self.run(**inputs)
 
  236         butlerQC.put(outputs, outputRefs)
 
  238     def run(self, fakeCat, ccdVisitFakeMagnitudes, diffIm, associatedDiaSources, band):
 
  239         """Match fakes to detected diaSources within a difference image bound. 
  243         fakeCat : `pandas.DataFrame` 
  244             Catalog of fakes to match to detected diaSources. 
  245         diffIm : `lsst.afw.image.Exposure` 
  246             Difference image where ``associatedDiaSources`` were detected in. 
  247         associatedDiaSources : `pandas.DataFrame` 
  248             Catalog of difference image sources detected in ``diffIm``. 
  252         result : `lsst.pipe.base.Struct` 
  253             Results struct with components. 
  255             - ``matchedDiaSources`` : Fakes matched to input diaSources. Has 
  256               length of ``fakeCat``. (`pandas.DataFrame`) 
  258         self.computeExpectedDiffMag(fakeCat, ccdVisitFakeMagnitudes, band)
 
  259         return super().
run(fakeCat, diffIm, associatedDiaSources)
 
  261     def computeExpectedDiffMag(self, fakeCat, ccdVisitFakeMagnitudes, band):
 
  262         """Compute the magnitude expected in the difference image for this 
  263         detector/visit. Modify fakeCat in place. 
  265         Negative magnitudes indicate that the source should be detected as 
  270         fakeCat : `pandas.DataFrame` 
  271             Catalog of fake sources. 
  272         ccdVisitFakeMagnitudes : `pandas.DataFrame` 
  273             Magnitudes for variable sources in this specific ccdVisit. 
  275             Band that this ccdVisit was observed in. 
  277         magName = self.config.mag_col % band
 
  278         magnitudes = fakeCat[magName].to_numpy()
 
  279         visitMags = ccdVisitFakeMagnitudes[
"variableMag"].to_numpy()
 
  280         diffFlux = (visitMags * u.ABmag).to_value(u.nJy) - (magnitudes * u.ABmag).to_value(u.nJy)
 
  281         diffMag = np.where(diffFlux > 0,
 
  282                            (diffFlux * u.nJy).to_value(u.ABmag),
 
  283                            -(-diffFlux * u.nJy).to_value(u.ABmag))
 
  285         noVisit = ~fakeCat[
"isVisitSource"]
 
  286         noTemplate = ~fakeCat[
"isTemplateSource"]
 
  287         both = np.logical_and(fakeCat[
"isVisitSource"],
 
  288                               fakeCat[
"isTemplateSource"])
 
  290         fakeCat.loc[noVisit, magName] = -magnitudes[noVisit]
 
  291         fakeCat.loc[noTemplate, magName] = visitMags[noTemplate]
 
  292         fakeCat.loc[both, magName] = diffMag[both]
 
A floating-point coordinate rectangle geometry.
Point in an unspecified spherical coordinate system.
def run(self, coaddExposures, bbox, wcs)