Coverage for python / lsst / analysis / tools / actions / keyedData / keyedDataActions.py: 34%
85 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-15 00:23 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-15 00:23 +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/>.
21from __future__ import annotations
23__all__ = (
24 "ChainedKeyedDataActions",
25 "AddComputedVector",
26 "KeyedDataSelectorAction",
27 "KeyedScalars",
28 "KeyedDataKeyAccessAction",
29 "KeyedDataUnitAccessAction",
30)
32from typing import Any, Optional, cast
34import numpy as np
35from lsst.pex.config import Field
36from lsst.pex.config.configurableActions import ConfigurableActionField, ConfigurableActionStructField
37from lsst.pex.config.listField import ListField
39from ...interfaces import (
40 KeyedData,
41 KeyedDataAction,
42 KeyedDataSchema,
43 Scalar,
44 ScalarAction,
45 Vector,
46 VectorAction,
47)
50class ChainedKeyedDataActions(KeyedDataAction):
51 r"""Run a series of `KeyedDataAction`\ s and accumulated their output into
52 one KeyedData result.
53 """
55 keyedDataActions = ConfigurableActionStructField[KeyedDataAction](
56 doc="Set of KeyedData actions to run, results will be concatenated into a final output KeyedData"
57 "object"
58 )
60 def getInputSchema(self) -> KeyedDataSchema:
61 for action in self.keyedDataActions:
62 yield from action.getInputSchema()
64 def getOutputSchema(self) -> KeyedDataSchema:
65 for action in self.keyedDataActions:
66 output = action.getOutputSchema()
67 if output is not None:
68 yield from output
70 def __call__(self, data: KeyedData, **kwargs) -> KeyedData:
71 result: KeyedData = {} # type:ignore
72 for action in self.keyedDataActions:
73 for column, values in action(data, **kwargs).items():
74 result[column] = values
75 return result
78class AddComputedVector(KeyedDataAction):
79 """Compute a `Vector` from the specified `VectorAction` and add it to a
80 copy of the KeyedData, returning the result.
81 """
83 action = ConfigurableActionField[VectorAction](doc="Action to use to compute Vector")
84 keyName = Field[str](doc="Key name to add to KeyedData")
86 def getInputSchema(self) -> KeyedDataSchema:
87 yield from self.action.getInputSchema()
89 def getOutputSchema(self) -> KeyedDataSchema:
90 return ((self.keyName, Vector),)
92 def __call__(self, data: KeyedData, **kwargs) -> KeyedData:
93 result = dict(data)
94 result[self.keyName.format(**kwargs)] = self.action(data, **kwargs)
95 return result
98class KeyedDataSelectorAction(KeyedDataAction):
99 """Extract Vector specified by ``vectorKeys`` from input KeyedData and
100 optionally apply selectors to down select extracted vectors.
102 Note this action will not work with keyed scalars, see `getInputSchema` for
103 expected schema.
104 """
106 vectorKeys = ListField[str](doc="Keys to extract from KeyedData and return", default=[])
108 selectors = ConfigurableActionStructField[VectorAction](
109 doc="Selectors for selecting rows, will be AND together",
110 )
112 def getInputSchema(self) -> KeyedDataSchema:
113 yield from ((column, Vector | Scalar) for column in self.vectorKeys) # type: ignore
114 for action in self.selectors:
115 yield from action.getInputSchema()
117 def getOutputSchema(self) -> KeyedDataSchema:
118 return ((column, Vector | Scalar) for column in self.vectorKeys) # type: ignore
120 def __call__(self, data: KeyedData, **kwargs) -> KeyedData:
121 mask: Optional[np.ndarray] = None
122 for selector in self.selectors:
123 subMask = selector(data, **kwargs)
124 if mask is None:
125 mask = subMask
126 else:
127 mask *= subMask # type: ignore
128 result = {}
129 for key in self.vectorKeys:
130 key_arg = key.format_map(kwargs)
131 result[f"{self.identity}_{key_arg}"] = data[key_arg]
132 if mask is not None:
133 return {key: cast(Vector, col)[mask] for key, col in result.items()}
134 else:
135 return result
138class KeyedScalars(KeyedDataAction):
139 """Creates an output of type KeyedData, where the keys are given by the
140 identifiers in `scalarActions` and the values are the results of the
141 corresponding `ScalarAction`.
142 """
144 scalarActions = ConfigurableActionStructField[ScalarAction](
145 doc="Create a KeyedData of individual ScalarActions"
146 )
148 def getInputSchema(self) -> KeyedDataSchema:
149 for action in self.scalarActions:
150 yield from action.getInputSchema()
152 def getOutputSchema(self) -> KeyedDataSchema:
153 for name in self.scalarActions.fieldNames:
154 yield (name, Scalar)
156 def __call__(self, data: KeyedData, **kwargs) -> KeyedData:
157 result: KeyedData = {} # type: ignore
158 for name, action in self.scalarActions.items():
159 result[name] = action(data, **kwargs)
160 return result
163class KeyedDataKeyAccessAction(KeyedDataAction):
164 """Fetches the value for a given top-level key in a KeyedData.
166 Note that the returned value may optionally be a KeyedData itself.
167 """
169 topLevelKey = Field[str](doc="The top-level key in the KeyedData structure to extract data from.")
171 def getInputSchema(self) -> KeyedDataSchema:
172 yield from [(self.topLevelKey, Any)]
174 def __call__(self, data: KeyedData, **kwargs) -> Any:
175 return data[self.topLevelKey]
178class KeyedDataUnitAccessAction(KeyedDataAction):
179 """Fetches the unit for a given key in a KeyedData as a string.
181 If the requested key doesn't have a unit, an empty string is returned.
182 """
184 key = Field[str](doc="The key in the KeyedData structure to extract the unit from.")
186 def getInputSchema(self) -> KeyedDataSchema:
187 yield from [(self.key, Any)]
189 def __call__(self, data: KeyedData, **kwargs) -> str:
190 if data[self.key.format_map(kwargs)].unit is not None:
191 return data[self.key.format_map(kwargs)].unit.to_string()
192 else:
193 return ""