Coverage for python/lsst/analysis/tools/analysisParts/base.py: 19%
92 statements
« prev ^ index » next coverage.py v6.4.4, created at 2022-08-30 03:25 -0700
« prev ^ index » next coverage.py v6.4.4, created at 2022-08-30 03:25 -0700
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/>.
21from __future__ import annotations
23__all__ = ("BasePrep", "BaseProcess", "BaseMetricAction")
25from collections import abc
26from typing import Mapping
28import astropy.units as apu
29from lsst.pex.config.dictField import DictField
30from lsst.pipe.tasks.configurableActions import ConfigurableActionStructField
31from lsst.verify import Measurement
33from ..actions.keyedData import KeyedDataSelectorAction
34from ..interfaces import (
35 AnalysisAction,
36 KeyedData,
37 KeyedDataAction,
38 KeyedDataSchema,
39 KeyedDataTypes,
40 MetricAction,
41 Scalar,
42 Vector,
43 VectorAction,
44)
47class BasePrep(KeyedDataSelectorAction):
48 def addInputSchema(self, inputSchema: KeyedDataSchema) -> None:
49 self._frozen = False
50 self.vectorKeys = [name for name, _ in inputSchema]
51 self._frozen = True
54class BaseProcess(KeyedDataAction):
55 buildActions = ConfigurableActionStructField[VectorAction | KeyedDataAction](
56 doc="Actions which compute a Vector which will be added to results"
57 )
58 filterActions = ConfigurableActionStructField[VectorAction | KeyedDataAction](
59 doc="Actions which filter one or more input or build Vectors into shorter vectors"
60 )
61 calculateActions = ConfigurableActionStructField[AnalysisAction](
62 doc="Actions which compute quantities from the input or built data"
63 )
65 def getInputSchema(self) -> KeyedDataSchema:
66 inputSchema: KeyedDataTypes = {} # type: ignore
67 buildOutputSchema: KeyedDataTypes = {} # type: ignore
68 filterOutputSchema: KeyedDataTypes = {} # type: ignore
70 for fieldName, action in self.buildActions.items():
71 for name, typ in action.getInputSchema():
72 inputSchema[name] = typ
73 if isinstance(action, KeyedDataAction):
74 buildOutputSchema.update(action.getOutputSchema() or {})
75 else:
76 buildOutputSchema[fieldName] = Vector
78 for fieldName, action in self.filterActions.items():
79 for name, typ in action.getInputSchema():
80 if name not in buildOutputSchema:
81 inputSchema[name] = typ
82 if isinstance(action, KeyedDataAction):
83 filterOutputSchema.update(action.getOutputSchema() or {})
84 else:
85 filterOutputSchema[fieldName] = Vector
87 for action in self.calculateActions:
88 for name, typ in action.getInputSchema():
89 if name not in buildOutputSchema and name not in filterOutputSchema:
90 inputSchema[name] = typ
91 return ((name, typ) for name, typ in inputSchema.items())
93 def getOutputSchema(self) -> KeyedDataSchema:
94 for action in self.buildActions:
95 if isinstance(action, KeyedDataAction):
96 outSchema = action.getOutputSchema()
97 if outSchema is not None:
98 yield from outSchema
100 def __call__(self, data: KeyedData, **kwargs) -> KeyedData:
101 results = {}
102 data = dict(data)
103 for name, action in self.buildActions.items():
104 match action(data, **kwargs):
105 case abc.Mapping() as item:
106 for key, result in item.items():
107 results[key] = result
108 case item:
109 results[name] = item
110 view1 = data | results
111 for name, action in self.filterActions.items():
112 match action(view1, **kwargs):
113 case abc.Mapping() as item:
114 for key, result in item.items():
115 results[key] = result
116 case item:
117 results[name] = item
119 view2 = data | results
120 for name, action in self.calculateActions.items():
121 match action(view2, **kwargs):
122 case abc.Mapping() as item:
123 for key, result in item.items():
124 results[key] = result
125 case item:
126 results[name] = item
127 return results
130class BaseMetricAction(MetricAction):
131 units = DictField[str, str](
132 doc="Mapping of scalar key to astropy unit string",
133 )
134 newNames = DictField[str, str](
135 doc="Mapping of key to new name if needed prior to creating metric",
136 default={},
137 )
139 def getInputSchema(self) -> KeyedDataSchema:
140 # Something is wrong with the typing for DictField key iteration
141 return [(key, Scalar) for key in self.units] # type: ignore
143 def __call__(self, data: KeyedData, **kwargs) -> Mapping[str, Measurement] | Measurement:
144 results = {}
145 for key, unit in self.units.items():
146 formattedKey = key.format(**kwargs)
147 if formattedKey not in data:
148 raise ValueError(f"Key: {formattedKey} could not be found input data")
149 value = data[formattedKey]
150 if not isinstance(value, Scalar):
151 raise ValueError(f"Data for key {key} is not a Scalar type")
152 if newName := self.newNames.get(key):
153 formattedKey = newName.format(**kwargs)
154 results[formattedKey] = Measurement(formattedKey, value * apu.Unit(unit))
155 return results