Coverage for python/lsst/atmospec/centroiding.py: 30%
80 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-03 10:37 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-03 10:37 +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
104 self.astrometry.sourceSelector["science"].doRequirePrimary = False
105 self.astrometry.sourceSelector["science"].doIsolated = False
108class SingleStarCentroidTask(pipeBase.PipelineTask):
109 """XXX Docs here
110 """
112 ConfigClass = SingleStarCentroidTaskConfig
113 _DefaultName = 'singleStarCentroid'
115 def __init__(self, initInputs=None, **kwargs):
116 super().__init__(**kwargs)
118 self.makeSubtask("astrometry", refObjLoader=None)
119 self.makeSubtask('qfmTask')
121 def runQuantum(self, butlerQC, inputRefs, outputRefs):
122 inputs = butlerQC.get(inputRefs)
123 refObjLoader = ReferenceObjectLoader(dataIds=[ref.datasetRef.dataId
124 for ref in inputRefs.astromRefCat],
125 refCats=inputs.pop('astromRefCat'),
126 name=self.config.connections.astromRefCat,
127 config=self.config.astromRefObjLoader, log=self.log)
129 refObjLoader.pixelMargin = 1000
130 self.astrometry.setRefObjLoader(refObjLoader)
132 # See L603 (def runQuantum(self, butlerQC, inputRefs, outputRefs):)
133 # in calibrate.py to put photocal back in
135 outputs = self.run(**inputs)
136 butlerQC.put(outputs, outputRefs)
138 def run(self, inputExp, inputSources):
139 """XXX Docs
140 """
142 # TODO: Change this to doing this the proper way
143 referenceFilterName = self.config.referenceFilterOverride
144 referenceFilterLabel = afwImage.FilterLabel(physical=referenceFilterName, band=referenceFilterName)
145 # there's a better way of doing this with the task I think
146 originalFilterLabel = inputExp.getFilter()
147 inputExp.setFilter(referenceFilterLabel)
148 originalWcs = inputExp.getWcs()
150 successfulFit = False
151 try:
152 astromResult = self.astrometry.run(sourceCat=inputSources, exposure=inputExp)
153 scatter = astromResult.scatterOnSky.asArcseconds()
154 inputExp.setFilter(originalFilterLabel)
155 if scatter < 1:
156 successfulFit = True
157 except (RuntimeError, TaskError, IndexError, ValueError, AttributeError) as e:
158 # IndexError raised for low source counts:
159 # index 0 is out of bounds for axis 0 with size 0
161 # ValueError: negative dimensions are not allowed
162 # seen when refcat source count is low (but non-zero)
164 # AttributeError: 'NoneType' object has no attribute 'asArcseconds'
165 # when the result is a failure as the wcs is set to None on failure
166 self.log.warn(f"Solving failed: {e}")
167 inputExp.setWcs(originalWcs) # this is set to None when the fit fails, so restore it
168 finally:
169 inputExp.setFilter(originalFilterLabel) # always restore this
171 centroid = None
172 if successfulFit:
173 target = inputExp.visitInfo.object
174 centroid = getTargetCentroidFromWcs(inputExp, target, logger=self.log)
175 if not centroid:
176 successfulFit = False
177 self.log.warning(f'Failed to find target centroid for {target} via WCS')
178 if not centroid:
179 result = self.qfmTask.run(inputExp)
180 centroid = result.brightestObjCentroid
182 centroidTuple = (centroid[0], centroid[1]) # unify Point2D or tuple to tuple
183 self.log.info(f"Centroid of main star found at {centroidTuple} found"
184 f" via {'astrometry' if successfulFit else 'QuickFrameMeasurement'}")
185 result = pipeBase.Struct(atmospecCentroid={'centroid': centroidTuple,
186 'astrometricMatch': successfulFit})
187 return result