Coverage for python/lsst/analysis/tools/actions/vector/calcBinnedStats.py: 48%
72 statements
« prev ^ index » next coverage.py v7.5.0, created at 2024-04-26 04:07 -0700
« prev ^ index » next coverage.py v7.5.0, created at 2024-04-26 04:07 -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__ = ("CalcBinnedStatsAction",)
25from functools import cached_property
26from typing import cast
28import numpy as np
29from lsst.pex.config import Field
30from lsst.pex.config.configurableActions import ConfigurableActionField
32from ...interfaces import KeyedData, KeyedDataAction, KeyedDataSchema, Scalar, Vector
33from ..keyedData.summaryStatistics import SummaryStatisticAction
34from .selectors import RangeSelector
37class CalcBinnedStatsAction(KeyedDataAction):
38 key_vector = Field[str](doc="Vector on which to compute statistics")
39 name_prefix = Field[str](default="", doc="Field name to append stat names to")
40 name_suffix = Field[str](default="", doc="Field name to append to stat names")
41 selector_range = ConfigurableActionField[RangeSelector](doc="Range selector")
42 return_minmax = Field[bool](default=True, doc="Whether to return the bin minimum and maximum")
44 def getInputSchema(self, **kwargs) -> KeyedDataSchema:
45 yield (self.key_vector, Vector)
46 yield from self.selector_range.getInputSchema()
48 def getOutputSchema(self) -> KeyedDataSchema:
49 yield from (
50 (self.name_mask, Vector),
51 (self.name_median, Scalar),
52 (self.name_sigmaMad, Scalar),
53 (self.name_count, Scalar),
54 )
55 if self.return_minmax:
56 yield (self.name_select_maximum, Scalar)
57 yield (self.name_select_median, Scalar),
58 if self.return_minmax:
59 yield (self.name_select_minimum, Scalar),
60 yield from (
61 ("range_maximum", Scalar),
62 ("range_minimum", Scalar),
63 )
65 @cached_property
66 def name_count(self):
67 return f"{self.name_prefix}count{self.name_suffix}"
69 @cached_property
70 def name_mask(self):
71 return f"{self.name_prefix}mask{self.name_suffix}"
73 @cached_property
74 def name_median(self):
75 return f"{self.name_prefix}median{self.name_suffix}"
77 @cached_property
78 def name_select_maximum(self):
79 return f"{self.name_prefix}select_maximum{self.name_suffix}"
81 @cached_property
82 def name_select_median(self):
83 return f"{self.name_prefix}select_median{self.name_suffix}"
85 @cached_property
86 def name_select_minimum(self):
87 return f"{self.name_prefix}select_minimum{self.name_suffix}"
89 @cached_property
90 def name_sigmaMad(self):
91 return f"{self.name_prefix}sigmaMad{self.name_suffix}"
93 def __call__(self, data: KeyedData, **kwargs) -> KeyedData:
94 has_band = "band" in kwargs
95 kwargs_format = {}
96 if has_band:
97 kwargs_format["band"] = kwargs["band"]
98 prefix_band = f"{kwargs['band']}_" if has_band else ""
100 results = {}
101 mask = self.selector_range(data, **kwargs)
102 results[self.name_mask.format(**kwargs_format)] = mask
103 kwargs["mask"] = mask
105 action = SummaryStatisticAction(vectorKey=self.key_vector)
106 # this is sad, but pex_config seems to have broken behavior that
107 # is dangerous to fix
108 action.setDefaults()
110 for name, value in action(data, **kwargs).items():
111 results[getattr(self, f"name_{name}").format(**kwargs_format)] = value
113 values = cast(Vector, data[self.selector_range.vectorKey][mask]) # type: ignore
114 valid = np.sum(np.isfinite(values)) > 0
116 if self.return_minmax:
117 results[self.name_select_maximum.format(**kwargs_format)] = cast(
118 Scalar, float(np.nanmax(values)) if valid else np.nan
119 )
120 results[self.name_select_median.format(**kwargs_format)] = cast(
121 Scalar, float(np.nanmedian(values)) if valid else np.nan
122 )
123 if self.return_minmax:
124 results[self.name_select_minimum.format(**kwargs_format)] = cast(
125 Scalar, float(np.nanmin(values)) if valid else np.nan
126 )
127 results[f"{prefix_band}range_maximum"] = self.selector_range.maximum
128 results[f"{prefix_band}range_minimum"] = self.selector_range.minimum
130 return results