Coverage for python / lsst / obs / base / filters.py: 54%
56 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-14 23:50 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-14 23:50 +0000
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.
51 Unique for each piece of glass. This should match the exact filter name
52 used in the observatory's metadata.
53 This name is used to define the ``physical_filter`` Butler Dimension.
55 If neither `band` nor `afw_name` is defined, this is used
56 as the `~lsst.afw.image.FilterLabel` ``name``, otherwise it is added to the
57 list of `~lsst.afw.image.FilterLabel` 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
63 HSC).
65 Not all filters have an abstract filter: engineering or test filters may
66 not have a generically-termed filter name.
68 If specified and if `afw_name` is None, this is used as the
69 `~lsst.afw.image.FilterLabel` ``name`` field, otherwise it is added to the
70 list of `~lsst.afw.image.FilterLabel` aliases.
71 """
73 doc: str | None = None
74 """A short description of this filter, possibly with a link to more
75 information.
76 """
78 afw_name: str | None = None
79 """If not None, the name of the `~lsst.afw.image.Filter` object.
81 This is distinct from physical_filter and band to maintain
82 backwards compatibility in some obs packages.
83 For example, for HSC there are two distinct ``r`` and ``i`` filters, named
84 ``r/r2`` and ``i/i2``.
85 """
87 alias: Set[str] = frozenset()
88 """Alternate names for this filter. These are added to the
89 `~lsst.afw.image.Filter` alias list.
90 """
92 def __post_init__(self) -> None:
93 # force alias to be immutable, so that hashing works
94 if not isinstance(self.alias, frozenset): 94 ↛ 95line 94 didn't jump to line 95 because the condition on line 94 was never true
95 object.__setattr__(self, "alias", frozenset(self.alias))
97 def __str__(self) -> str:
98 txt = f"FilterDefinition(physical_filter='{self.physical_filter}'"
99 if self.band is not None:
100 txt += f", band='{self.band}'"
101 if self.afw_name is not None:
102 txt += f", afw_name='{self.afw_name}'"
103 if len(self.alias) != 0:
104 txt += f", alias='{self.alias}'"
105 return txt + ")"
107 def makeFilterLabel(self) -> lsst.afw.image.FilterLabel:
108 """Create a complete FilterLabel for this filter."""
109 return lsst.afw.image.FilterLabel(band=self.band, physical=self.physical_filter)
112class FilterDefinitionCollection(Sequence[FilterDefinition]):
113 """An order-preserving collection of multiple `FilterDefinition`.
115 Parameters
116 ----------
117 *filters : `FilterDefinition`
118 The filters in this collection.
119 """
121 physical_to_band: dict[str, str | None]
122 """A mapping from physical filter name to band name.
123 This is a convenience feature to allow file readers to create a FilterLabel
124 when reading a raw file that only has a physical filter name, without
125 iterating over the entire collection.
126 """
128 def __init__(self, *filters: FilterDefinition):
129 self._filters = list(filters)
130 self.physical_to_band = {filter.physical_filter: filter.band for filter in self._filters}
132 @overload
133 def __getitem__(self, i: int) -> FilterDefinition:
134 pass
136 @overload
137 def __getitem__(self, s: slice) -> Sequence[FilterDefinition]:
138 pass
140 def __getitem__(self, index: Any) -> Any:
141 return self._filters[index]
143 def __len__(self) -> int:
144 return len(self._filters)
146 def __str__(self) -> str:
147 return "FilterDefinitions(" + ", ".join(str(f) for f in self._filters) + ")"
149 def findAll(self, name: str) -> set[FilterDefinition]:
150 """Return the FilterDefinitions that match a particular name.
152 This method makes no attempt to prioritize, e.g., band names over
153 physical filter names; any definition that makes *any* reference
154 to the name is returned.
156 Parameters
157 ----------
158 name : `str`
159 The name to search for. May be any band, physical, or alias name.
161 Returns
162 -------
163 matches : `set` [`FilterDefinition`]
164 All FilterDefinitions containing ``name`` as one of their
165 filter names.
166 """
167 matches = set()
168 for filter in self._filters:
169 if (
170 name == filter.physical_filter
171 or name == filter.band
172 or name == filter.afw_name
173 or name in filter.alias
174 ):
175 matches.add(filter)
176 return matches