Coverage for tests/test_actions.py: 16%
224 statements
« prev ^ index » next coverage.py v6.4.2, created at 2022-08-06 10:00 +0000
« prev ^ index » next coverage.py v6.4.2, created at 2022-08-06 10:00 +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/>.
22import unittest
24import astropy.units as u
25import numpy as np
26import pandas as pd
27from lsst.analysis.tools.actions.scalar.scalarActions import (
28 ApproxFloor,
29 CountAction,
30 MeanAction,
31 MedianAction,
32 SigmaMadAction,
33 StdevAction,
34)
35from lsst.analysis.tools.actions.vector.calcShapeSize import CalcShapeSize
36from lsst.analysis.tools.actions.vector.selectors import (
37 CoaddPlotFlagSelector,
38 FlagSelector,
39 GalaxySelector,
40 SkyObjectSelector,
41 SnSelector,
42 StarSelector,
43 VectorSelector,
44)
45from lsst.analysis.tools.actions.vector.vectorActions import (
46 DownselectVector,
47 ExtinctionCorrectedMagDiff,
48 FractionalDifference,
49 LoadVector,
50 MagColumnNanoJansky,
51 MagDiff,
52)
55class TestScalarActions(unittest.TestCase):
56 """ "Test ScalarActions"""
58 def setUp(self):
59 x = np.arange(100, dtype=float)
60 x[31] = np.nan
61 x[41] = np.nan
62 x[59] = np.nan
63 y = x**2
64 self.data = {"r_y": x, "i_y": y}
65 self.mask = np.zeros(100, dtype=bool)
66 self.mask[1:50] = 1
68 def _testScalarActionAlmostEqual(self, cls, truth, maskedTruth):
69 action = cls(vectorKey="{band}_y")
70 schema = [col for col, colType in action.getInputSchema()]
71 self.assertEqual(schema, ["{band}_y"])
72 result = action(self.data, band="i")
73 self.assertAlmostEqual(result, truth)
74 result = action(self.data, mask=self.mask, band="i")
75 self.assertAlmostEqual(result, maskedTruth)
77 def testMedianAction(self):
78 self._testScalarActionAlmostEqual(MedianAction, 2500, 576)
80 def testMeanAction(self):
81 self._testScalarActionAlmostEqual(MeanAction, 3321.9278350515465, 803.8936170212766)
83 def testStdevAction(self):
84 self._testScalarActionAlmostEqual(StdevAction, 2984.5855649976297, 733.5989754407842)
86 def testSigmaMadAction(self):
87 self._testScalarActionAlmostEqual(SigmaMadAction, 3278.033505115886, 759.0923358748682)
89 def testCountAction(self):
90 self._testScalarActionAlmostEqual(CountAction, 97, 47)
92 def testApproxFloorAction(self):
93 self._testScalarActionAlmostEqual(ApproxFloor, 9216.0, 2352.5)
96class TestVectorActions(unittest.TestCase):
97 """Test VectorActions"""
99 def setUp(self):
100 r = np.arange(5) + 1
101 i = r**2
102 rFlag = np.ones(5)
103 rFlag[1] = 0
104 iFlag = np.ones(5)
105 iFlag[3] = 0
106 data = {
107 "r_vector": r,
108 "i_vector": i,
109 "r_flag": rFlag,
110 "i_flag": iFlag,
111 "g_vector": 3 * r,
112 "E(B-V)": r[::-1],
113 "r_ixx": r**2,
114 "r_iyy": r[::-1] ** 2,
115 "r_ixy": r * r[::-1],
116 "g_iixx": r**2,
117 "g_iiyy": r[::-1] ** 2,
118 "g_iixy": r * r[::-1],
119 }
121 self.data = pd.DataFrame.from_dict(data)
123 def _checkSchema(self, action, truth):
124 schema = sorted([col for col, colType in action.getInputSchema()])
125 self.assertEqual(schema, truth)
127 def testDownselectVector(self):
128 selector = FlagSelector(selectWhenTrue=["{band}_flag"])
129 action = DownselectVector(vectorKey="{band}_vector", selector=selector)
130 result = action(self.data, band="r")
131 self._checkSchema(action, ["{band}_flag", "{band}_vector"])
132 np.testing.assert_array_equal(result, np.array([1, 3, 4, 5]))
134 def testMagColumnNanoJansky(self):
135 truth = [
136 31.40006562228223,
137 29.894915643962324,
138 29.014459348683918,
139 28.389765665642418,
140 27.905215600602137,
141 ]
142 action = MagColumnNanoJansky(vectorKey="{band}_vector")
143 result = action(self.data, band="i")
144 self._checkSchema(action, ["{band}_vector"])
145 np.testing.assert_array_almost_equal(result, truth)
147 def testFractionalDifference(self):
148 actionA = LoadVector(vectorKey="{band1}_vector")
149 actionB = LoadVector(vectorKey="{band2}_vector")
150 truth = [0.0, -0.5, -0.6666666666666666, -0.75, -0.8]
151 diff = FractionalDifference(actionA=actionA, actionB=actionB)
152 result = diff(self.data, band1="r", band2="i")
153 self._checkSchema(diff, ["{band1}_vector", "{band2}_vector"])
154 np.testing.assert_array_almost_equal(result, truth)
156 def testMagDiff(self):
157 # Use the same units as the data so that we know the difference exactly
158 # without conversions.
159 # testExtinctionCorrectedMagDiff will test the conversions
160 truth = np.array(2 * self.data["r_vector"]) * u.ABmag
161 action = MagDiff(
162 col1="{band1}_vector",
163 col2="{band2}_vector",
164 fluxUnits1="mag(AB)",
165 fluxUnits2="mag(AB)",
166 returnMillimags=False,
167 )
168 result = action(self.data, band1="g", band2="r")
169 self._checkSchema(action, ["{band1}_vector", "{band2}_vector"])
170 np.testing.assert_array_almost_equal(result, truth.value)
172 def testExtinctionCorrectedMagDiff(self):
173 for returnMillimags in (True, False):
174 # Check that conversions are working properly
175 magDiff = MagDiff(
176 col1="{band1}_vector",
177 col2="{band2}_vector",
178 fluxUnits1="jansky",
179 fluxUnits2="jansky",
180 returnMillimags=returnMillimags,
181 )
182 action = ExtinctionCorrectedMagDiff(
183 magDiff=magDiff,
184 band1="g",
185 band2="r",
186 ebvCol="E(B-V)",
187 extinctionCoeffs={"g": 0.2, "r": 1.5},
188 )
190 result = action(self.data, band1="g", band2="r")
191 lhs = (np.array(self.data["g_vector"]) * u.jansky).to(u.ABmag)
192 rhs = (np.array(self.data["r_vector"]) * u.jansky).to(u.ABmag)
193 diff = lhs - rhs
194 correction = np.array((0.2 - 1.5) * self.data["E(B-V)"]) * u.mag
195 if returnMillimags:
196 diff = diff.to(u.mmag)
197 correction = correction.to(u.mmag)
198 truth = diff - correction
199 self._checkSchema(action, ["E(B-V)", "{band1}_vector", "{band2}_vector"])
200 np.testing.assert_array_almost_equal(result, truth.value)
202 # Test with hard coded bands
203 magDiff = MagDiff(
204 col1="g_vector",
205 col2="r_vector",
206 fluxUnits1="jansky",
207 fluxUnits2="jansky",
208 returnMillimags=False,
209 )
210 action = ExtinctionCorrectedMagDiff(
211 magDiff=magDiff,
212 ebvCol="E(B-V)",
213 extinctionCoeffs={"g": 0.2, "r": 1.5},
214 )
215 result = action(self.data)
216 lhs = (np.array(self.data["g_vector"]) * u.jansky).to(u.ABmag)
217 rhs = (np.array(self.data["r_vector"]) * u.jansky).to(u.ABmag)
218 diff = lhs - rhs
219 correction = np.array((0.2 - 1.5) * self.data["E(B-V)"]) * u.mag
220 truth = diff - correction
221 self._checkSchema(action, ["E(B-V)", "g_vector", "r_vector"])
222 np.testing.assert_array_almost_equal(result, truth.value)
224 def testCalcShapeSize(self):
225 xx = self.data["r_ixx"]
226 yy = self.data["r_iyy"]
227 xy = self.data["r_ixy"]
229 # Test determinant with defaults
230 action = CalcShapeSize()
231 result = action(self.data, band="r")
232 schema = [col for col, colType in action.getInputSchema()]
233 self.assertEqual(sorted(schema), ["{band}_ixx", "{band}_ixy", "{band}_iyy"])
234 truth = 0.25 * (xx * yy - xy**2)
235 np.testing.assert_array_almost_equal(result, truth)
237 # Test trace with columns specified
238 action = CalcShapeSize(
239 colXx="{band}_iixx",
240 colYy="{band}_iiyy",
241 colXy="{band}_iixy",
242 sizeType="trace",
243 )
244 result = action(self.data, band="g")
245 schema = [col for col, colType in action.getInputSchema()]
246 self.assertEqual(sorted(schema), ["{band}_iixx", "{band}_iiyy"])
247 truth = np.sqrt(0.5 * (xx + yy))
248 np.testing.assert_array_almost_equal(result, truth)
251class TestVectorSelectors(unittest.TestCase):
252 def setUp(self):
253 self.size = 20
254 falseFlags = {
255 "{band}_psfFlux_flag": [1],
256 "{band}_pixelFlags_saturatedCenter": [3],
257 "{band}_extendedness_flag": [5],
258 "xy_flag": [7],
259 "i_pixelFlags_edge": [13],
260 "r_pixelFlags_edge": [15],
261 "sky_object": [13, 15, 17],
262 }
264 trueFlags = {
265 "detect_isPatchInner": [9],
266 "detect_isDeblendedSource": [11],
267 }
269 flux = np.arange(self.size) * 10
270 fluxErr = np.ones(self.size) * 0.1
271 extendedness = np.arange(20) / 20 - 0.1
273 self.data = {
274 "r_psfFlux": flux,
275 "r_psfFluxErr": fluxErr,
276 "i_cmodelFlux": flux[::-1],
277 "i_cmodelFluxError": fluxErr[::-1],
278 "r_cmodelFlux": flux,
279 "r_cmodelFluxError": fluxErr,
280 "i_extendedness": extendedness,
281 "i_extended": extendedness,
282 }
283 bands = ("r", "i")
284 for band in bands:
285 for flag, bits in falseFlags.items():
286 vector = np.zeros(self.size, dtype=bool)
287 for bit in bits:
288 vector[bit] = 1
289 self.data[flag.format(band=band)] = vector
291 for flag, bits in trueFlags.items():
292 vector = np.ones(self.size, dtype=bool)
293 for bit in bits:
294 vector[bit] = 0
295 self.data[flag.format(band=band)] = vector
297 def _checkSchema(self, action, truth):
298 schema = [col for col, colType in action.getInputSchema()]
299 self.assertEqual(sorted(schema), sorted(truth))
301 def testFlagSelector(self):
302 selector = FlagSelector(
303 selectWhenFalse=["{band}_psfFlux_flag"], selectWhenTrue=["detect_isPatchInner"]
304 )
305 self._checkSchema(selector, ["detect_isPatchInner", "{band}_psfFlux_flag"])
306 result = selector(self.data, band="r")
307 truth = np.ones(self.size, dtype=bool)
308 truth[1] = False
309 truth[9] = False
310 np.testing.assert_array_equal(result, truth)
312 def testCoaddPlotFlagSelector(self):
313 # Test defaults
314 selector = CoaddPlotFlagSelector()
315 keys = [
316 "{band}_psfFlux_flag",
317 "{band}_pixelFlags_saturatedCenter",
318 "{band}_extendedness_flag",
319 "xy_flag",
320 "detect_isPatchInner",
321 "detect_isDeblendedSource",
322 ]
323 self._checkSchema(selector, keys)
325 result = selector(self.data)
326 truth = np.ones(self.size, dtype=bool)
327 for bit in (1, 3, 5, 7, 9, 11):
328 truth[bit] = 0
329 np.testing.assert_array_equal(result, truth)
331 # Test bands override
332 selector = CoaddPlotFlagSelector(
333 bands=["i", "r"],
334 selectWhenFalse=["{band}_psfFlux_flag"],
335 selectWhenTrue=["detect_isDeblendedSource"],
336 )
337 self._checkSchema(selector, ["{band}_psfFlux_flag", "detect_isDeblendedSource"])
338 result = selector(self.data)
339 truth = np.ones(self.size, dtype=bool)
340 for bit in (1, 11):
341 truth[bit] = 0
342 np.testing.assert_array_equal(result, truth)
344 def testSnSelector(self):
345 # test defaults
346 selector = SnSelector()
347 keys = [
348 "{band}_psfFlux",
349 "{band}_psfFluxErr",
350 ]
351 self._checkSchema(selector, keys)
352 result = selector(self.data, bands=["r"])
353 truth = np.ones(self.size, dtype=bool)
354 truth[:6] = 0
355 np.testing.assert_array_equal(result, truth)
357 # test overrides
358 selector = SnSelector(
359 fluxType="{band}_cmodelFlux",
360 threshold=200.0,
361 uncertaintySuffix="Error",
362 bands=["r", "i"],
363 )
364 keys = [
365 "{band}_cmodelFlux",
366 "{band}_cmodelFluxError",
367 ]
368 self._checkSchema(selector, keys)
369 result = selector(self.data)
370 truth = np.ones(self.size, dtype=bool)
371 truth[:3] = 0
372 truth[-3:] = 0
373 np.testing.assert_array_equal(result, truth)
375 def testSkyObjectSelector(self):
376 # Test default
377 selector = SkyObjectSelector()
378 keys = ["{band}_pixelFlags_edge", "sky_object"]
379 self._checkSchema(selector, keys)
380 result = selector(self.data)
381 truth = np.zeros(self.size, dtype=bool)
382 truth[15] = 1
383 truth[17] = 1
384 np.testing.assert_array_equal(result, truth)
386 # Test overrides
387 selector = SkyObjectSelector(bands=["i", "r"])
388 self._checkSchema(selector, keys)
389 result = selector(self.data)
390 truth = np.zeros(self.size, dtype=bool)
391 truth[17] = 1
392 np.testing.assert_array_equal(result, truth)
394 def testStarSelector(self):
395 # test default
396 selector = StarSelector()
397 self._checkSchema(selector, ["{band}_extendedness"])
398 result = selector(self.data, band="i")
399 truth = (self.data["i_extendedness"] >= 0) & (self.data["i_extendedness"] < 0.5)
400 np.testing.assert_array_almost_equal(result, truth)
402 # Test overrides
403 selector = StarSelector(vectorKey="i_extended", extendedness_maximum=0.3)
404 result = selector(self.data, band="i")
405 truth = (self.data["i_extendedness"] >= 0) & (self.data["i_extendedness"] < 0.3)
406 np.testing.assert_array_almost_equal(result, truth)
408 def testGalaxySelector(self):
409 # test default
410 selector = GalaxySelector()
411 self._checkSchema(selector, ["{band}_extendedness"])
412 result = selector(self.data, band="i")
413 truth = self.data["i_extendedness"] > 0.5
414 np.testing.assert_array_almost_equal(result, truth)
416 # Test overrides
417 selector = GalaxySelector(vectorKey="i_extended", extendedness_minimum=0.3)
418 result = selector(self.data, band="i")
419 truth = self.data["i_extendedness"] > 0.3
420 np.testing.assert_array_almost_equal(result, truth)
422 def testVectorSelector(self):
423 selector = VectorSelector(vectorKey="{band}_psfFlux_flag")
424 self._checkSchema(selector, ["{band}_psfFlux_flag"])
425 result = selector(self.data, band="i")
426 truth = np.zeros(self.size, dtype=bool)
427 truth[1] = True
428 np.testing.assert_array_equal(result, truth)