Coverage for python/lsst/analysis/tools/tasks/astrometricCatalogMatch.py: 33%
83 statements
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-20 13:17 +0000
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-20 13:17 +0000
1# This file is part of analysis_tools.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
22__all__ = (
23 "AstrometricCatalogMatchConfig",
24 "AstrometricCatalogMatchTask",
25 "AstrometricCatalogMatchVisitConfig",
26 "AstrometricCatalogMatchVisitTask",
27)
29import lsst.geom
30import lsst.pex.config as pexConfig
31import lsst.pipe.base as pipeBase
32import numpy as np
33from astropy.table import Table
34from astropy.time import Time
35from lsst.pex.config.configurableActions import ConfigurableActionStructField
36from lsst.pipe.tasks.loadReferenceCatalog import LoadReferenceCatalogTask
38from ..actions.vector import StarSelector, VisitPlotFlagSelector
39from ..tasks.catalogMatch import CatalogMatchConfig, CatalogMatchConnections, CatalogMatchTask
42class AstrometricCatalogMatchConfig(CatalogMatchConfig, pipelineConnections=CatalogMatchConnections):
43 bands = pexConfig.ListField[str](
44 doc="The bands to persist downstream",
45 default=["u", "g", "r", "i", "z", "y"],
46 )
48 def setDefaults(self):
49 super().setDefaults()
50 self.matchesRefCat = True
51 self.referenceCatalogLoader.doApplyColorTerms = False
52 self.referenceCatalogLoader.refObjLoader.requireProperMotion = True
53 self.referenceCatalogLoader.refObjLoader.anyFilterMapsToThis = "phot_g_mean"
56class AstrometricCatalogMatchTask(CatalogMatchTask):
57 """Match a tract-level catalog to a reference catalog"""
59 ConfigClass = AstrometricCatalogMatchConfig
60 _DefaultName = "analysisToolsAstrometricCatalogMatch"
62 def runQuantum(self, butlerQC, inputRefs, outputRefs):
63 # Docs inherited from base class
65 inputs = butlerQC.get(inputRefs)
66 columns = self.prepColumns(self.config.bands)
67 table = inputs["catalog"].get(parameters={"columns": columns})
69 tract = butlerQC.quantum.dataId["tract"]
71 loaderTask = LoadReferenceCatalogTask(
72 config=self.config.referenceCatalogLoader,
73 dataIds=[ref.dataId for ref in inputRefs.refCat],
74 name=inputs["refCat"][0].ref.datasetType.name,
75 refCats=inputs["refCat"],
76 )
78 skymap = inputs.pop("skymap")
79 loadedRefCat = self._loadRefCat(loaderTask, skymap[tract])
80 outputs = self.run(targetCatalog=table, refCatalog=loadedRefCat, bands=self.config.bands)
82 butlerQC.put(outputs, outputRefs)
85class AstrometricCatalogMatchVisitConnections(
86 pipeBase.PipelineTaskConnections,
87 dimensions=("visit",),
88 defaultTemplates={"targetCatalog": "sourceTable_visit", "refCatalog": "gaia_dr3_20230707"},
89):
90 catalog = pipeBase.connectionTypes.Input(
91 doc="The visit-wide catalog to make plots from.",
92 storageClass="ArrowAstropy",
93 name="{targetCatalog}",
94 dimensions=("visit",),
95 deferLoad=True,
96 )
98 refCat = pipeBase.connectionTypes.PrerequisiteInput(
99 doc="The astrometry reference catalog to match to loaded input catalog sources.",
100 name="{refCatalog}",
101 storageClass="SimpleCatalog",
102 dimensions=("skypix",),
103 deferLoad=True,
104 multiple=True,
105 )
107 visitSummaryTable = pipeBase.connectionTypes.Input(
108 doc="A summary table of the ccds in the visit",
109 storageClass="ExposureCatalog",
110 name="visitSummary",
111 dimensions=("visit",),
112 )
114 matchedCatalog = pipeBase.connectionTypes.Output(
115 doc="Catalog with matched target and reference objects with separations",
116 name="{targetCatalog}_{refCatalog}_match",
117 storageClass="ArrowAstropy",
118 dimensions=("visit",),
119 )
122class AstrometricCatalogMatchVisitConfig(
123 AstrometricCatalogMatchConfig, pipelineConnections=AstrometricCatalogMatchVisitConnections
124):
125 selectorActions = ConfigurableActionStructField(
126 doc="Which selectors to use to narrow down the data for QA plotting.",
127 default={"flagSelector": VisitPlotFlagSelector},
128 )
130 extraColumns = pexConfig.ListField[str](
131 doc="Other catalog columns to persist to downstream tasks",
132 default=["psfFlux", "psfFluxErr"],
133 )
135 bands = pexConfig.ListField[str](
136 doc="The bands to persist downstream",
137 default=[],
138 )
140 def setDefaults(self):
141 self.matchesRefCat = True
142 # sourceSelectorActions.sourceSelector is StarSelector
143 self.sourceSelectorActions.sourceSelector = StarSelector()
144 self.sourceSelectorActions.sourceSelector.vectorKey = "extendedness"
145 # extraColumnSelectors.selector1 is SnSelector
146 self.extraColumnSelectors.selector1.fluxType = "psfFlux"
147 # extraColumnSelectors.selector2 is GalaxySelector
148 self.extraColumnSelectors.selector2.vectorKey = "extendedness"
149 self.extraColumnSelectors.selector3.vectorKey = "extendedness"
150 self.extraColumnSelectors.selector4 = VisitPlotFlagSelector
151 self.referenceCatalogLoader.doApplyColorTerms = False
152 self.referenceCatalogLoader.refObjLoader.requireProperMotion = False
153 self.referenceCatalogLoader.refObjLoader.anyFilterMapsToThis = "phot_g_mean"
156class AstrometricCatalogMatchVisitTask(AstrometricCatalogMatchTask):
157 """Match a visit-level catalog to a reference catalog"""
159 ConfigClass = AstrometricCatalogMatchVisitConfig
160 _DefaultName = "analysisToolsAstrometricCatalogMatchVisit"
162 def runQuantum(self, butlerQC, inputRefs, outputRefs):
163 # Docs inherited from base class
165 inputs = butlerQC.get(inputRefs)
167 columns = ["coord_ra", "coord_dec", "detector"] + self.config.extraColumns.list()
168 for selectorAction in [
169 self.config.selectorActions,
170 self.config.sourceSelectorActions,
171 self.config.extraColumnSelectors,
172 ]:
173 for selector in selectorAction:
174 selectorSchema = selector.getFormattedInputSchema()
175 columns += [s[0] for s in selectorSchema]
177 table = inputs["catalog"].get(parameters={"columns": columns})
179 loaderTask = LoadReferenceCatalogTask(
180 config=self.config.referenceCatalogLoader,
181 dataIds=[ref.dataId for ref in inputRefs.refCat],
182 name=inputs["refCat"][0].ref.datasetType.name,
183 refCats=inputs["refCat"],
184 )
186 visitSummaryTable = inputs.pop("visitSummaryTable")
187 loadedRefCat = self._loadRefCat(loaderTask, visitSummaryTable)
188 outputs = self.run(targetCatalog=table, refCatalog=loadedRefCat, bands=self.config.bands)
190 butlerQC.put(outputs, outputRefs)
192 def _loadRefCat(self, loaderTask, visitSummaryTable):
193 """Make a reference catalog with coordinates in degrees
195 Parameters
196 ----------
197 visitSummaryTable : `lsst.afw.table.ExposureCatalog`
198 The table of visit information
199 """
200 # Get convex hull around the detectors, then get its center and radius
201 corners = []
202 for visSum in visitSummaryTable:
203 for ra, dec in zip(visSum["raCorners"], visSum["decCorners"]):
204 # If the coordinates are nan then don't keep going
205 # because it crashes later
206 if not np.isfinite(ra) or not np.isfinite(dec):
207 raise pipeBase.NoWorkFound("Visit summary corners not finite")
208 corners.append(lsst.geom.SpherePoint(ra, dec, units=lsst.geom.degrees).getVector())
209 visitBoundingCircle = lsst.sphgeom.ConvexPolygon.convexHull(corners).getBoundingCircle()
210 center = lsst.geom.SpherePoint(visitBoundingCircle.getCenter())
211 radius = visitBoundingCircle.getOpeningAngle()
213 # Get the observation date of the visit
214 obsDate = visSum.getVisitInfo().getDate()
215 epoch = Time(obsDate.toPython())
217 # Load the reference catalog in the skyCircle of the detectors, then
218 # convert the coordinates to degrees and convert the catalog to a
219 # dataframe
221 filterName = self.config.referenceCatalogLoader.refObjLoader.anyFilterMapsToThis
222 loadedRefCat = loaderTask.getSkyCircleCatalog(center, radius, filterName, epoch=epoch)
224 return Table(loadedRefCat)