Coverage for python/lsst/atmospec/centroiding.py: 31%
78 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-06 11:56 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-06 11:56 +0000
1# This file is part of atmospec.
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/>.
22import lsst.afw.image as afwImage
23import lsst.pipe.base as pipeBase
25from lsst.meas.algorithms import LoadReferenceObjectsConfig, MagnitudeLimit, ReferenceObjectLoader
26from lsst.meas.astrom import AstrometryTask, FitAffineWcsTask
27from lsst.pipe.tasks.quickFrameMeasurement import (QuickFrameMeasurementTask)
28from lsst.pipe.base.task import TaskError
29import lsst.pipe.base.connectionTypes as cT
30import lsst.pex.config as pexConfig
32from .utils import getTargetCentroidFromWcs
34__all__ = ['SingleStarCentroidTaskConfig', 'SingleStarCentroidTask']
37class SingleStarCentroidTaskConnections(pipeBase.PipelineTaskConnections,
38 dimensions=("instrument", "visit", "detector")):
39 inputExp = cT.Input(
40 name="icExp",
41 doc="Image-characterize output exposure",
42 storageClass="ExposureF",
43 dimensions=("instrument", "visit", "detector"),
44 multiple=False,
45 )
46 inputSources = cT.Input(
47 name="icSrc",
48 doc="Image-characterize output sources.",
49 storageClass="SourceCatalog",
50 dimensions=("instrument", "visit", "detector"),
51 multiple=False,
52 )
53 astromRefCat = cT.PrerequisiteInput(
54 doc="Reference catalog to use for astrometry",
55 name="gaia_dr2_20200414",
56 storageClass="SimpleCatalog",
57 dimensions=("skypix",),
58 deferLoad=True,
59 multiple=True,
60 )
61 atmospecCentroid = cT.Output(
62 name="atmospecCentroid",
63 doc="The main star centroid in yaml format.",
64 storageClass="StructuredDataDict",
65 dimensions=("instrument", "visit", "detector"),
66 )
69class SingleStarCentroidTaskConfig(pipeBase.PipelineTaskConfig,
70 pipelineConnections=SingleStarCentroidTaskConnections):
71 """Configuration parameters for ProcessStarTask."""
72 astromRefObjLoader = pexConfig.ConfigField(
73 dtype=LoadReferenceObjectsConfig,
74 doc="Reference object loader config for astrometric calibration.",
75 )
76 astrometry = pexConfig.ConfigurableField(
77 target=AstrometryTask,
78 doc="Task to perform astrometric calibration to refine the WCS",
79 )
80 qfmTask = pexConfig.ConfigurableField(
81 target=QuickFrameMeasurementTask,
82 doc="XXX",
83 )
84 referenceFilterOverride = pexConfig.Field(
85 dtype=str,
86 doc="Which filter in the reference catalog to match to?",
87 default="phot_g_mean"
88 )
90 def setDefaults(self):
91 super().setDefaults()
92 self.astromRefObjLoader.pixelMargin = 1000
94 self.astrometry.wcsFitter.retarget(FitAffineWcsTask)
95 self.astrometry.referenceSelector.doMagLimit = True
96 magLimit = MagnitudeLimit()
97 magLimit.minimum = 1
98 magLimit.maximum = 15
99 self.astrometry.referenceSelector.magLimit = magLimit
100 self.astrometry.referenceSelector.magLimit.fluxField = "phot_g_mean_flux"
101 self.astrometry.matcher.maxRotationDeg = 5.99
102 self.astrometry.matcher.maxOffsetPix = 3000
103 self.astrometry.sourceSelector['matcher'].minSnr = 10
106class SingleStarCentroidTask(pipeBase.PipelineTask):
107 """XXX Docs here
108 """
110 ConfigClass = SingleStarCentroidTaskConfig
111 _DefaultName = 'singleStarCentroid'
113 def __init__(self, initInputs=None, **kwargs):
114 super().__init__(**kwargs)
116 self.makeSubtask("astrometry", refObjLoader=None)
117 self.makeSubtask('qfmTask')
119 def runQuantum(self, butlerQC, inputRefs, outputRefs):
120 inputs = butlerQC.get(inputRefs)
121 refObjLoader = ReferenceObjectLoader(dataIds=[ref.datasetRef.dataId
122 for ref in inputRefs.astromRefCat],
123 refCats=inputs.pop('astromRefCat'),
124 name=self.config.connections.astromRefCat,
125 config=self.config.astromRefObjLoader, log=self.log)
127 refObjLoader.pixelMargin = 1000
128 self.astrometry.setRefObjLoader(refObjLoader)
130 # See L603 (def runQuantum(self, butlerQC, inputRefs, outputRefs):)
131 # in calibrate.py to put photocal back in
133 outputs = self.run(**inputs)
134 butlerQC.put(outputs, outputRefs)
136 def run(self, inputExp, inputSources):
137 """XXX Docs
138 """
140 # TODO: Change this to doing this the proper way
141 referenceFilterName = self.config.referenceFilterOverride
142 referenceFilterLabel = afwImage.FilterLabel(physical=referenceFilterName, band=referenceFilterName)
143 # there's a better way of doing this with the task I think
144 originalFilterLabel = inputExp.getFilter()
145 inputExp.setFilter(referenceFilterLabel)
146 originalWcs = inputExp.getWcs()
148 successfulFit = False
149 try:
150 astromResult = self.astrometry.run(sourceCat=inputSources, exposure=inputExp)
151 scatter = astromResult.scatterOnSky.asArcseconds()
152 inputExp.setFilter(originalFilterLabel)
153 if scatter < 1:
154 successfulFit = True
155 except (RuntimeError, TaskError, IndexError, ValueError, AttributeError) as e:
156 # IndexError raised for low source counts:
157 # index 0 is out of bounds for axis 0 with size 0
159 # ValueError: negative dimensions are not allowed
160 # seen when refcat source count is low (but non-zero)
162 # AttributeError: 'NoneType' object has no attribute 'asArcseconds'
163 # when the result is a failure as the wcs is set to None on failure
164 self.log.warn(f"Solving failed: {e}")
165 inputExp.setWcs(originalWcs) # this is set to None when the fit fails, so restore it
166 finally:
167 inputExp.setFilter(originalFilterLabel) # always restore this
169 centroid = None
170 if successfulFit:
171 target = inputExp.visitInfo.object
172 centroid = getTargetCentroidFromWcs(inputExp, target, logger=self.log)
173 if not centroid:
174 successfulFit = False
175 self.log.warning(f'Failed to find target centroid for {target} via WCS')
176 if not centroid:
177 result = self.qfmTask.run(inputExp)
178 centroid = result.brightestObjCentroid
180 centroidTuple = (centroid[0], centroid[1]) # unify Point2D or tuple to tuple
181 self.log.info(f"Centroid of main star found at {centroidTuple} found"
182 f" via {'astrometry' if successfulFit else 'QuickFrameMeasurement'}")
183 result = pipeBase.Struct(atmospecCentroid={'centroid': centroidTuple,
184 'astrometricMatch': successfulFit})
185 return result