Coverage for python/lsst/obs/base/filters.py: 58%
57 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-03-30 02:57 -0700
« prev ^ index » next coverage.py v7.4.4, created at 2024-03-30 02:57 -0700
1# This file is part of obs_base.
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/>.
22"""Classes to allow obs packages to define the filters used by an Instrument
23and for use by `lsst.afw.image.Filter`, gen2 dataIds, and gen3 Dimensions.
24"""
26from __future__ import annotations
28__all__ = ("FilterDefinition", "FilterDefinitionCollection")
30import dataclasses
31from collections.abc import Sequence, Set
32from typing import Any, overload
34import lsst.afw.image.utils
37@dataclasses.dataclass(frozen=True)
38class FilterDefinition:
39 """The definition of an instrument's filter bandpass.
41 This class is used to declare ``physical_filter`` and ``band``
42 information for an instrument.
44 This class is likely temporary, until we have a better versioned filter
45 definition system that includes complete transmission information.
46 """
48 physical_filter: str
49 """The name of a filter associated with a particular instrument: unique for
50 each piece of glass. This should match the exact filter name used in the
51 observatory's metadata.
53 This name is used to define the ``physical_filter`` gen3 Butler Dimension.
55 If neither ``band`` or ``afw_name`` is defined, this is used
56 as the `~lsst.afw.image.Filter` ``name``, otherwise it is added to the
57 list of `~lsst.afw.image.Filter` aliases.
58 """
60 band: str | None = None
61 """The generic name of a filter not associated with a particular instrument
62 (e.g. `r` for the SDSS Gunn r-band, which could be on SDSS, LSST, or HSC).
64 Not all filters have an abstract filter: engineering or test filters may
65 not have a generically-termed filter name.
67 If specified and if `afw_name` is None, this is used as the
68 `~lsst.afw.image.Filter` ``name`` field, otherwise it is added to the list
69 of `~lsst.afw.image.Filter` aliases.
70 """
72 doc: str | None = None
73 """A short description of this filter, possibly with a link to more
74 information.
75 """
77 afw_name: str | None = None
78 """If not None, the name of the `~lsst.afw.image.Filter` object.
80 This is distinct from physical_filter and band to maintain
81 backwards compatibility in some obs packages.
82 For example, for HSC there are two distinct ``r`` and ``i`` filters, named
83 ``r/r2`` and ``i/i2``.
84 """
86 alias: Set[str] = frozenset()
87 """Alternate names for this filter. These are added to the
88 `~lsst.afw.image.Filter` alias list.
89 """
91 def __post_init__(self) -> None:
92 # force alias to be immutable, so that hashing works
93 if not isinstance(self.alias, frozenset): 93 ↛ 94line 93 didn't jump to line 94, because the condition on line 93 was never true
94 object.__setattr__(self, "alias", frozenset(self.alias))
96 def __str__(self) -> str:
97 txt = f"FilterDefinition(physical_filter='{self.physical_filter}'"
98 if self.band is not None:
99 txt += f", band='{self.band}'"
100 if self.afw_name is not None:
101 txt += f", afw_name='{self.afw_name}'"
102 if len(self.alias) != 0:
103 txt += f", alias='{self.alias}'"
104 return txt + ")"
106 def makeFilterLabel(self) -> lsst.afw.image.FilterLabel:
107 """Create a complete FilterLabel for this filter."""
108 return lsst.afw.image.FilterLabel(band=self.band, physical=self.physical_filter)
111class FilterDefinitionCollection(Sequence[FilterDefinition]):
112 """An order-preserving collection of multiple `FilterDefinition`.
114 Parameters
115 ----------
116 filters : `Sequence`
117 The filters in this collection.
118 """
120 physical_to_band: dict[str, str | None]
121 """A mapping from physical filter name to band name.
122 This is a convenience feature to allow file readers to create a FilterLabel
123 when reading a raw file that only has a physical filter name, without
124 iterating over the entire collection.
125 """
127 def __init__(self, *filters: FilterDefinition):
128 self._filters = list(filters)
129 self.physical_to_band = {filter.physical_filter: filter.band for filter in self._filters}
131 @overload
132 def __getitem__(self, i: int) -> FilterDefinition:
133 pass
135 @overload
136 def __getitem__(self, s: slice) -> Sequence[FilterDefinition]:
137 pass
139 def __getitem__(self, index: Any) -> Any:
140 return self._filters[index]
142 def __len__(self) -> int:
143 return len(self._filters)
145 def __str__(self) -> str:
146 return "FilterDefinitions(" + ", ".join(str(f) for f in self._filters) + ")"
148 def findAll(self, name: str) -> set[FilterDefinition]:
149 """Return the FilterDefinitions that match a particular name.
151 This method makes no attempt to prioritize, e.g., band names over
152 physical filter names; any definition that makes *any* reference
153 to the name is returned.
155 Parameters
156 ----------
157 name : `str`
158 The name to search for. May be any band, physical, or alias name.
160 Returns
161 -------
162 matches : `set` [`FilterDefinition`]
163 All FilterDefinitions containing ``name`` as one of their
164 filter names.
165 """
166 matches = set()
167 for filter in self._filters:
168 if (
169 name == filter.physical_filter
170 or name == filter.band
171 or name == filter.afw_name
172 or name in filter.alias
173 ):
174 matches.add(filter)
175 return matches