Coverage for python/lsst/obs/base/filters.py: 55%
56 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-02-02 14:44 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2023-02-02 14:44 +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 typing import AbstractSet, Any, Dict, Optional, Sequence, Set, overload
33import lsst.afw.image.utils
36@dataclasses.dataclass(frozen=True)
37class FilterDefinition:
38 """The definition of an instrument's filter bandpass.
40 This class is used to declare ``physical_filter`` and ``band``
41 information for an instrument.
43 This class is likely temporary, until we have a better versioned filter
44 definition system that includes complete transmission information.
45 """
47 physical_filter: str
48 """The name of a filter associated with a particular instrument: unique for
49 each piece of glass. This should match the exact filter name used in the
50 observatory's metadata.
52 This name is used to define the ``physical_filter`` gen3 Butler Dimension.
54 If neither ``band`` or ``afw_name`` is defined, this is used
55 as the `~lsst.afw.image.Filter` ``name``, otherwise it is added to the
56 list of `~lsst.afw.image.Filter` aliases.
57 """
59 band: Optional[str] = None
60 """The generic name of a filter not associated with a particular instrument
61 (e.g. `r` for the SDSS Gunn r-band, which could be on SDSS, LSST, or HSC).
63 Not all filters have an abstract filter: engineering or test filters may
64 not have a genericly-termed filter name.
66 If specified and if `afw_name` is None, this is used as the
67 `~lsst.afw.image.Filter` ``name`` field, otherwise it is added to the list
68 of `~lsst.afw.image.Filter` aliases.
69 """
71 doc: Optional[str] = None
72 """A short description of this filter, possibly with a link to more
73 information.
74 """
76 afw_name: Optional[str] = None
77 """If not None, the name of the `~lsst.afw.image.Filter` object.
79 This is distinct from physical_filter and band to maintain
80 backwards compatibility in some obs packages.
81 For example, for HSC there are two distinct ``r`` and ``i`` filters, named
82 ``r/r2`` and ``i/i2``.
83 """
85 alias: AbstractSet[str] = frozenset()
86 """Alternate names for this filter. These are added to the
87 `~lsst.afw.image.Filter` alias list.
88 """
90 def __post_init__(self) -> None:
91 # force alias to be immutable, so that hashing works
92 if not isinstance(self.alias, frozenset): 92 ↛ 93line 92 didn't jump to line 93, because the condition on line 92 was never true
93 object.__setattr__(self, "alias", frozenset(self.alias))
95 def __str__(self) -> str:
96 txt = f"FilterDefinition(physical_filter='{self.physical_filter}'"
97 if self.band is not None:
98 txt += f", band='{self.band}'"
99 if self.afw_name is not None:
100 txt += f", afw_name='{self.afw_name}'"
101 if len(self.alias) != 0:
102 txt += f", alias='{self.alias}'"
103 return txt + ")"
105 def makeFilterLabel(self) -> lsst.afw.image.FilterLabel:
106 """Create a complete FilterLabel for this filter."""
107 return lsst.afw.image.FilterLabel(band=self.band, physical=self.physical_filter)
110class FilterDefinitionCollection(Sequence[FilterDefinition]):
111 """An order-preserving collection of multiple `FilterDefinition`.
113 Parameters
114 ----------
115 filters : `Sequence`
116 The filters in this collection.
117 """
119 physical_to_band: Dict[str, Optional[str]]
120 """A mapping from physical filter name to band name.
121 This is a convenience feature to allow file readers to create a FilterLabel
122 when reading a raw file that only has a physical filter name, without
123 iterating over the entire collection.
124 """
126 def __init__(self, *filters: FilterDefinition):
127 self._filters = list(filters)
128 self.physical_to_band = {filter.physical_filter: filter.band for filter in self._filters}
130 @overload
131 def __getitem__(self, i: int) -> FilterDefinition:
132 pass
134 @overload
135 def __getitem__(self, s: slice) -> Sequence[FilterDefinition]:
136 pass
138 def __getitem__(self, index: Any) -> Any:
139 return self._filters[index]
141 def __len__(self) -> int:
142 return len(self._filters)
144 def __str__(self) -> str:
145 return "FilterDefinitions(" + ", ".join(str(f) for f in self._filters) + ")"
147 def findAll(self, name: str) -> Set[FilterDefinition]:
148 """Return the FilterDefinitions that match a particular name.
150 This method makes no attempt to prioritize, e.g., band names over
151 physical filter names; any definition that makes *any* reference
152 to the name is returned.
154 Parameters
155 ----------
156 name : `str`
157 The name to search for. May be any band, physical, or alias name.
159 Returns
160 -------
161 matches : `set` [`FilterDefinition`]
162 All FilterDefinitions containing ``name`` as one of their
163 filter names.
164 """
165 matches = set()
166 for filter in self._filters:
167 if (
168 name == filter.physical_filter
169 or name == filter.band
170 or name == filter.afw_name
171 or name in filter.alias
172 ):
173 matches.add(filter)
174 return matches