Coverage for python/lsst/analysis/tools/tasks/astrometricCatalogMatch.py: 33%
84 statements
« prev ^ index » next coverage.py v7.5.1, created at 2024-05-16 04:38 -0700
« prev ^ index » next coverage.py v7.5.1, created at 2024-05-16 04:38 -0700
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"
54 self.connections.refCatalog = "gaia_dr3_20230707"
57class AstrometricCatalogMatchTask(CatalogMatchTask):
58 """Match a tract-level catalog to a reference catalog"""
60 ConfigClass = AstrometricCatalogMatchConfig
61 _DefaultName = "analysisToolsAstrometricCatalogMatch"
63 def runQuantum(self, butlerQC, inputRefs, outputRefs):
64 # Docs inherited from base class
66 inputs = butlerQC.get(inputRefs)
67 columns = self.prepColumns(self.config.bands)
68 table = inputs["catalog"].get(parameters={"columns": columns})
70 tract = butlerQC.quantum.dataId["tract"]
72 loaderTask = LoadReferenceCatalogTask(
73 config=self.config.referenceCatalogLoader,
74 dataIds=[ref.dataId for ref in inputRefs.refCat],
75 name=inputs["refCat"][0].ref.datasetType.name,
76 refCats=inputs["refCat"],
77 )
79 skymap = inputs.pop("skymap")
80 loadedRefCat = self._loadRefCat(loaderTask, skymap[tract])
81 outputs = self.run(targetCatalog=table, refCatalog=loadedRefCat, bands=self.config.bands)
83 butlerQC.put(outputs, outputRefs)
86class AstrometricCatalogMatchVisitConnections(
87 pipeBase.PipelineTaskConnections,
88 dimensions=("visit",),
89 defaultTemplates={"targetCatalog": "sourceTable_visit", "refCatalog": "gaia_dr3_20230707"},
90):
91 catalog = pipeBase.connectionTypes.Input(
92 doc="The visit-wide catalog to make plots from.",
93 storageClass="ArrowAstropy",
94 name="{targetCatalog}",
95 dimensions=("visit",),
96 deferLoad=True,
97 )
99 refCat = pipeBase.connectionTypes.PrerequisiteInput(
100 doc="The astrometry reference catalog to match to loaded input catalog sources.",
101 name="{refCatalog}",
102 storageClass="SimpleCatalog",
103 dimensions=("skypix",),
104 deferLoad=True,
105 multiple=True,
106 )
108 visitSummaryTable = pipeBase.connectionTypes.Input(
109 doc="A summary table of the ccds in the visit",
110 storageClass="ExposureCatalog",
111 name="visitSummary",
112 dimensions=("visit",),
113 )
115 matchedCatalog = pipeBase.connectionTypes.Output(
116 doc="Catalog with matched target and reference objects with separations",
117 name="{targetCatalog}_{refCatalog}_match",
118 storageClass="ArrowAstropy",
119 dimensions=("visit",),
120 )
123class AstrometricCatalogMatchVisitConfig(
124 AstrometricCatalogMatchConfig, pipelineConnections=AstrometricCatalogMatchVisitConnections
125):
126 selectorActions = ConfigurableActionStructField(
127 doc="Which selectors to use to narrow down the data for QA plotting.",
128 default={"flagSelector": VisitPlotFlagSelector},
129 )
131 extraColumns = pexConfig.ListField[str](
132 doc="Other catalog columns to persist to downstream tasks",
133 default=["psfFlux", "psfFluxErr"],
134 )
136 bands = pexConfig.ListField[str](
137 doc="The bands to persist downstream",
138 default=[],
139 )
141 def setDefaults(self):
142 self.matchesRefCat = True
143 # sourceSelectorActions.sourceSelector is StarSelector
144 self.sourceSelectorActions.sourceSelector = StarSelector()
145 self.sourceSelectorActions.sourceSelector.vectorKey = "extendedness"
146 # extraColumnSelectors.selector1 is SnSelector
147 self.extraColumnSelectors.selector1.fluxType = "psfFlux"
148 # extraColumnSelectors.selector2 is GalaxySelector
149 self.extraColumnSelectors.selector2.vectorKey = "extendedness"
150 self.extraColumnSelectors.selector3.vectorKey = "extendedness"
151 self.extraColumnSelectors.selector4 = VisitPlotFlagSelector
152 self.referenceCatalogLoader.doApplyColorTerms = False
153 self.referenceCatalogLoader.refObjLoader.requireProperMotion = False
154 self.referenceCatalogLoader.refObjLoader.anyFilterMapsToThis = "phot_g_mean"
157class AstrometricCatalogMatchVisitTask(AstrometricCatalogMatchTask):
158 """Match a visit-level catalog to a reference catalog"""
160 ConfigClass = AstrometricCatalogMatchVisitConfig
161 _DefaultName = "analysisToolsAstrometricCatalogMatchVisit"
163 def runQuantum(self, butlerQC, inputRefs, outputRefs):
164 # Docs inherited from base class
166 inputs = butlerQC.get(inputRefs)
168 columns = ["coord_ra", "coord_dec", "detector"] + self.config.extraColumns.list()
169 for selectorAction in [
170 self.config.selectorActions,
171 self.config.sourceSelectorActions,
172 self.config.extraColumnSelectors,
173 ]:
174 for selector in selectorAction:
175 selectorSchema = selector.getFormattedInputSchema()
176 columns += [s[0] for s in selectorSchema]
178 table = inputs["catalog"].get(parameters={"columns": columns})
180 loaderTask = LoadReferenceCatalogTask(
181 config=self.config.referenceCatalogLoader,
182 dataIds=[ref.dataId for ref in inputRefs.refCat],
183 name=inputs["refCat"][0].ref.datasetType.name,
184 refCats=inputs["refCat"],
185 )
187 visitSummaryTable = inputs.pop("visitSummaryTable")
188 loadedRefCat = self._loadRefCat(loaderTask, visitSummaryTable)
189 outputs = self.run(targetCatalog=table, refCatalog=loadedRefCat, bands=self.config.bands)
191 butlerQC.put(outputs, outputRefs)
193 def _loadRefCat(self, loaderTask, visitSummaryTable):
194 """Make a reference catalog with coordinates in degrees
196 Parameters
197 ----------
198 visitSummaryTable : `lsst.afw.table.ExposureCatalog`
199 The table of visit information
200 """
201 # Get convex hull around the detectors, then get its center and radius
202 corners = []
203 for visSum in visitSummaryTable:
204 for ra, dec in zip(visSum["raCorners"], visSum["decCorners"]):
205 # If the coordinates are nan then don't keep going
206 # because it crashes later
207 if not np.isfinite(ra) or not np.isfinite(dec):
208 raise pipeBase.NoWorkFound("Visit summary corners not finite")
209 corners.append(lsst.geom.SpherePoint(ra, dec, units=lsst.geom.degrees).getVector())
210 visitBoundingCircle = lsst.sphgeom.ConvexPolygon.convexHull(corners).getBoundingCircle()
211 center = lsst.geom.SpherePoint(visitBoundingCircle.getCenter())
212 radius = visitBoundingCircle.getOpeningAngle()
214 # Get the observation date of the visit
215 obsDate = visSum.getVisitInfo().getDate()
216 epoch = Time(obsDate.toPython())
218 # Load the reference catalog in the skyCircle of the detectors, then
219 # convert the coordinates to degrees and convert the catalog to a
220 # dataframe
222 filterName = self.config.referenceCatalogLoader.refObjLoader.anyFilterMapsToThis
223 loadedRefCat = loaderTask.getSkyCircleCatalog(center, radius, filterName, epoch=epoch)
225 return Table(loadedRefCat)