Coverage for tests/test_contexts.py: 50%
92 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-02-24 02:03 -0800
« prev ^ index » next coverage.py v6.5.0, created at 2023-02-24 02:03 -0800
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 <http://www.gnu.org/licenses/>.
21from __future__ import annotations
23import warnings
24from typing import cast
25from unittest import TestCase, main
27import lsst.utils.tests
28import numpy as np
29from lsst.analysis.tools import (
30 AnalysisAction,
31 AnalysisTool,
32 KeyedData,
33 KeyedDataAction,
34 KeyedDataSchema,
35 Scalar,
36 ScalarAction,
37 Vector,
38)
39from lsst.analysis.tools.actions.scalar import MeanAction, MedianAction
40from lsst.analysis.tools.contexts import Context
41from lsst.pex.config import Field
42from lsst.pipe.tasks.configurableActions import ConfigurableActionField, ConfigurableActionStructField
45class MedianContext(Context):
46 """Test Context for median"""
48 pass
51class MeanContext(Context):
52 """Test Context for mean"""
54 pass
57class MultiplyContext(Context):
58 """Test Context to multiply results"""
60 pass
63class DivideContext(Context):
64 """Test Context to divide result"""
66 pass
69class TestAction1(KeyedDataAction):
70 multiple = ConfigurableActionStructField[ScalarAction](doc="Multiple Actions")
72 def getInputSchema(self) -> KeyedDataSchema:
73 return (("a", Vector),)
75 def medianContext(self) -> None:
76 self.multiple.a = MedianAction(vectorKey="a")
77 self.multiple.b = MedianAction(vectorKey="a")
78 self.multiple.c = MedianAction(vectorKey="a")
80 def meanContext(self) -> None:
81 self.multiple.a = MeanAction(vectorKey="a")
82 self.multiple.b = MeanAction(vectorKey="a")
84 def __call__(self, data: KeyedData, **kwargs) -> KeyedData:
85 result = np.array([action(data, **kwargs) for action in self.multiple])
86 return cast(KeyedData, {"b": result})
89class TestAction2(KeyedDataAction):
90 single = ConfigurableActionField[ScalarAction](doc="Single Action")
91 multiplier = ConfigurableActionField[AnalysisAction](doc="Multiplier")
93 def getInputSchema(self) -> KeyedDataSchema:
94 return (("b", Vector),)
96 def setDefaults(self) -> None:
97 super().setDefaults()
98 # This action remains constant in every context, but it will be
99 # recursively configured with any contexts
100 self.multiplier = TestAction3()
102 def medianContext(self) -> None:
103 self.single = MedianAction(vectorKey="b")
105 def meanContext(self) -> None:
106 self.single = MeanAction(vectorKey="b")
108 def __call__(self, data: KeyedData, **kwargs) -> KeyedData:
109 result = self.single(data, **kwargs)
110 result *= cast(Scalar, self.multiplier(cast(KeyedData, {"c": result}), **kwargs)["d"])
111 return {"c": result}
114class TestAction3(KeyedDataAction):
115 multiplier = Field[float](doc="Number to multiply result by")
117 def getInputSchema(self) -> KeyedDataSchema:
118 return (("c", Vector),)
120 def multiplyContext(self):
121 self.multiplier = 2
123 def divideContext(self):
124 self.multiplier = 0.5
126 def __call__(self, data: KeyedData, **kwargs) -> KeyedData:
127 return {"d": cast(Scalar, data["c"]) * self.multiplier}
130class TestAnalysisTool(AnalysisTool):
131 def setDefaults(self) -> None:
132 self.prep = TestAction1()
133 self.process = TestAction2()
134 self.produce = TestAction3()
137class ContextTestCase(TestCase):
138 def setUp(self) -> None:
139 super().setUp()
140 self.array = np.arange(20)
141 self.input = cast(KeyedData, {"a": self.array})
143 def testContext1(self):
144 tester = TestAnalysisTool()
145 # test applying contexts serially
146 tester.applyContext(MultiplyContext())
148 # verify assignment syntax works to support Yaml
149 # normally this should be called as a function in python
150 tester.applyContext = MedianContext
151 # cast below, because we are abusing AnalysisTool a bit for testing
152 # the final stage produces KeyedData instead of Measurement of Figure
153 with warnings.catch_warnings():
154 warnings.simplefilter("ignore")
155 result = cast(KeyedData, tester(self.input))
156 self.assertEqual(result["d"], 361)
158 def testContext2(self):
159 tester2 = TestAnalysisTool()
160 compound = MeanContext | DivideContext
161 tester2.applyContext(compound)
162 # cast below, because we are abusing AnalysisTool a bit for testing
163 # the final stage produces KeyedData instead of Measurement of Figure
164 with warnings.catch_warnings():
165 warnings.simplefilter("ignore")
166 result = cast(KeyedData, tester2(self.input))
167 self.assertEqual(result["d"], 22.5625)
170class MyMemoryTestCase(lsst.utils.tests.MemoryTestCase):
171 pass
174def setup_module(module):
175 lsst.utils.tests.init()
178if __name__ == "__main__": 178 ↛ 179line 178 didn't jump to line 179, because the condition on line 178 was never true
179 lsst.utils.tests.init()
180 main()