Coverage for python/lsst/analysis/tools/analysisParts/shapeSizeFractional.py: 28%
128 statements
« prev ^ index » next coverage.py v6.4.2, created at 2022-08-06 09:59 +0000
« prev ^ index » next coverage.py v6.4.2, created at 2022-08-06 09:59 +0000
1from __future__ import annotations
3from typing import cast
5import numpy as np
6from lsst.analysis.tools.actions.vector import LoadVector, MagColumnNanoJansky, VectorSelector
7from lsst.pex.config import Field
8from lsst.pipe.tasks.configurableActions import ConfigurableActionField, ConfigurableActionStructField
10from ..actions.keyedData import AddComputedVector, KeyedScalars
11from ..actions.plot.scatterplotWithTwoHists import ScatterPlotStatsAction
12from ..actions.scalar import CountAction, MedianAction, SigmaMadAction
13from ..actions.vector import (
14 CalcE,
15 CalcEDiff,
16 CalcShapeSize,
17 CoaddPlotFlagSelector,
18 DownselectVector,
19 FractionalDifference,
20 SnSelector,
21 StarSelector,
22)
23from ..interfaces import (
24 AnalysisAction,
25 AnalysisTool,
26 KeyedData,
27 KeyedDataAction,
28 KeyedDataSchema,
29 Scalar,
30 ScalarAction,
31 Vector,
32 VectorAction,
33)
36class _ApproxMedian(ScalarAction):
37 vectorKey = Field[str](doc="Key for the vector to perform action on", optional=False)
39 def getInputSchema(self) -> KeyedDataSchema:
40 return ((self.vectorKey, Vector),) # type: ignore
42 def __call__(self, data: KeyedData, **kwargs) -> Scalar:
43 mask = self.getMask(**kwargs)
44 value = np.sort(data[self.vectorKey.format(**kwargs)][mask]) # type: ignore
45 x = int(len(value) / 10)
46 return np.nanmedian(value[-x:])
49class ShapeSizeFractionalScalars(KeyedScalars):
50 vectorKey = Field[str](doc="Column key to compute scalars")
52 snFluxType = Field[str](doc="column key for the flux type used in SN selection")
54 selector = ConfigurableActionField[VectorAction](doc="Selector to use before computing Scalars")
56 def setDefaults(self):
57 super().setDefaults()
58 self.scalarActions.median = MedianAction(vectorKey=self.vectorKey) # type: ignore
59 self.scalarActions.sigmaMad = SigmaMadAction(vectorKey=self.vectorKey) # type: ignore
60 self.scalarActions.count = CountAction(vectorKey=self.vectorKey) # type: ignore
61 self.scalarActions.approxMag = _ApproxMedian(vectorKey=self.snFluxType) # type: ignore
63 def __call__(self, data: KeyedData, **kwargs) -> KeyedData:
64 mask = kwargs.get("mask")
65 selection = self.selector(data, **kwargs)
66 if mask is not None:
67 mask &= selection
68 else:
69 mask = selection
70 return super().__call__(data, **kwargs | dict(mask=mask))
73class ShapeSizeFractionalProcessLegacy(KeyedDataAction):
74 psfFluxShape = ConfigurableActionField[AnalysisAction](
75 doc="Action to calculate the PSF Shape",
76 )
77 shapeFracDif = ConfigurableActionField[AnalysisAction](
78 doc="Action which adds shapes to the KeyedData",
79 )
80 objectSelector = ConfigurableActionField[AnalysisAction](
81 doc="Action to select which objects should be considered"
82 )
83 highSNRSelector = ConfigurableActionField[AnalysisAction](
84 doc="Selector action add high SNR stars vector to output",
85 )
86 lowSNRSelector = ConfigurableActionField[AnalysisAction](
87 doc="Selector action add low SNR stars vector to output",
88 )
89 calculatorActions = ConfigurableActionStructField[KeyedDataAction](
90 doc="Actions which generate KeyedData of scalars",
91 )
92 magAction = ConfigurableActionField(doc="Action to generate the magnitude column")
94 def setDefaults(self):
95 # compute the PSF Shape
96 psfShapeName = "{band}_psfShape"
97 self.psfFluxShape = AddComputedVector()
98 self.psfFluxShape.keyName = psfShapeName
99 self.psfFluxShape.action = CalcShapeSize()
100 self.psfFluxShape.action.colXx = "{band}_ixxPSF"
101 self.psfFluxShape.action.colYy = "{band}_iyyPSF"
102 self.psfFluxShape.action.colXy = "{band}_ixyPSF"
104 # compute the Difference of shapes
105 self.shapeFracDif = AddComputedVector()
106 self.shapeFracDif.keyName = "{band}_derivedShape"
107 self.shapeFracDif.action = FractionalDifference()
108 self.shapeFracDif.action.actionA = CalcShapeSize()
109 self.shapeFracDif.action.actionB = LoadVector()
110 self.shapeFracDif.action.actionB.vectorKey = psfShapeName
112 # Default mag action action
113 self.magAction = MagColumnNanoJansky(vectorKey="{band}_psfFlux")
115 # Setup the selectors
116 self.objectSelector = StarSelector()
117 self.highSNRSelector = SnSelector(threshold=2700)
118 self.lowSNRSelector = SnSelector(threshold=500)
120 self.calculatorActions.highSNStars = ShapeSizeFractionalScalars(
121 vectorKey=self.shapeFracDif.keyName, snFluxType=self.highSNRSelector.fluxType
122 )
123 highSNSelector = VectorSelector(vectorKey="starHighSNMask")
124 self.calculatorActions.highSNStars.selector = highSNSelector
125 self.calculatorActions.lowSNStars = ShapeSizeFractionalScalars(
126 vectorKey=self.shapeFracDif.keyName, snFluxType=self.lowSNRSelector.fluxType
127 )
128 self.calculatorActions.lowSNStars.selector = VectorSelector(vectorKey="starLowSNMask")
130 for action in self.calculatorActions:
131 action.setDefaults()
133 super().setDefaults()
135 def getInputSchema(self) -> KeyedDataSchema:
136 yield from self.psfFluxShape.getInputSchema()
137 yield from self.objectSelector.getInputSchema()
138 yield from self.highSNRSelector.getInputSchema()
139 yield from self.lowSNRSelector.getInputSchema()
140 yield from self.shapeFracDif.getInputSchema()
141 for action in self.calculatorActions:
142 yield from action.getInputSchema()
144 def getOutputSchema(self) -> KeyedDataSchema:
145 results = (
146 ("starHighSNMask", Vector),
147 ("starLowSNMask", Vector),
148 ("xsStars", Vector),
149 ("ysStars", Vector),
150 )
151 for action in self.calculatorActions:
152 if isinstance(action, KeyedDataAction):
153 outputs = action.getOutputSchema()
154 if outputs is not None:
155 yield from outputs
156 yield from results
158 def __call__(self, data: KeyedData, **kwargs) -> KeyedData:
159 results = {}
160 data = self.psfFluxShape(data, **kwargs)
161 data = self.shapeFracDif(data, **kwargs)
163 objectMask = self.objectSelector(data, **kwargs)
164 highSNRMask = self.highSNRSelector(data, **(kwargs | {"mask": objectMask}))
165 lowSNRMask = self.lowSNRSelector(data, **kwargs | {"mask": objectMask})
167 data["starHighSNMask"] = highSNRMask
168 data["starLowSNMask"] = lowSNRMask
170 mags = self.magAction(data, **kwargs) # type: ignore
172 for name, action in self.calculatorActions.items(): # type: ignore
173 for key, value in action(data, **kwargs).items():
174 prefix = f"{band}_" if (band := kwargs.get("band")) else ""
175 newKey = f"{prefix}{name}_{key}"
176 results[newKey] = value
178 results["starHighSNMask"] = highSNRMask[objectMask]
179 results["starLowSNMask"] = lowSNRMask[objectMask]
180 results["xsStars"] = mags[objectMask]
181 shapeDiff = cast(Vector, data[cast(str, self.shapeFracDif.keyName.format(**kwargs))]) # type: ignore
182 results["ysStars"] = shapeDiff[objectMask]
183 return results
186class BasePsfResidualMixin(AnalysisTool):
187 """Shared configuration for `prep` and `process` stages of PSF residuals.
189 This is a mixin class used by `BasePsfResidualScatterPlot` and
190 `BasePsfResidualMetric` to share common default configuration.
191 """
193 def setDefaults(self):
194 super().setDefaults()
195 self.prep.selectors.flagSelector = CoaddPlotFlagSelector()
196 self.prep.selectors.snSelector = SnSelector(fluxType="{band}_psfFlux", threshold=100)
198 self.process.buildActions.mags = MagColumnNanoJansky(vectorKey="{band}_psfFlux")
199 self.process.buildActions.fracDiff = FractionalDifference(
200 actionA=CalcShapeSize(colXx="{band}_ixx", colYy="{band}_iyy", colXy="{band}_ixy"),
201 actionB=CalcShapeSize(colXx="{band}_ixxPSF", colYy="{band}_iyyPSF", colXy="{band}_ixyPSF"),
202 )
203 # Define an eDiff action and let e1Diff and e2Diff differ only in
204 # component.
205 self.process.buildActions.eDiff = CalcEDiff(
206 colA=CalcE(colXx="{band}_ixx", colYy="{band}_iyy", colXy="{band}_ixy"),
207 colB=CalcE(colXx="{band}_ixxPSF", colYy="{band}_iyyPSF", colXy="{band}_ixyPSF"),
208 )
209 self.process.buildActions.e1Diff = self.process.buildActions.eDiff
210 self.process.buildActions.e1Diff.component = "1"
211 self.process.buildActions.e2Diff = self.process.buildActions.eDiff
212 self.process.buildActions.e2Diff.component = "2"
213 # pre-compute a stellar selector mask so it can be used in the filter
214 # actions while only being computed once, alternatively the stellar
215 # selector could be calculated and applied twice in the filter stage
216 self.process.buildActions.starSelector = StarSelector()
218 self.process.filterActions.xStars = DownselectVector(
219 vectorKey="mags", selector=VectorSelector(vectorKey="starSelector")
220 )
221 # downselect the psfFlux as well
222 self.process.filterActions.psfFlux = DownselectVector(
223 vectorKey="{band}_psfFlux", selector=VectorSelector(vectorKey="starSelector")
224 )
225 self.process.filterActions.psfFluxErr = DownselectVector(
226 vectorKey="{band}_psfFluxErr", selector=VectorSelector(vectorKey="starSelector")
227 )
229 self.process.calculateActions.stars = ScatterPlotStatsAction(
230 vectorKey="yStars",
231 )
232 # use the downselected psfFlux
233 self.process.calculateActions.stars.highSNSelector.fluxType = "psfFlux"
234 self.process.calculateActions.stars.lowSNSelector.fluxType = "psfFlux"
235 self.process.calculateActions.stars.fluxType = "psfFlux"