Coverage for python/lsst/obs/base/filters.py: 57%
56 statements
« prev ^ index » next coverage.py v6.4.1, created at 2022-06-09 03:03 -0700
« prev ^ index » next coverage.py v6.4.1, created at 2022-06-09 03:03 -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 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 interface between the `~lsst.afw.image.Filter` class
41 and the Gen2 `~lsst.daf.persistence.CameraMapper` and Gen3
42 `~lsst.obs.base.Instruments` and ``physical_filter``/``band``
43 `~lsst.daf.butler.Dimension`.
45 This class is likely temporary, until we have a better versioned filter
46 definition system that includes complete transmission information.
47 """
49 physical_filter: str
50 """The name of a filter associated with a particular instrument: unique for
51 each piece of glass. This should match the exact filter name used in the
52 observatory's metadata.
54 This name is used to define the ``physical_filter`` gen3 Butler Dimension.
56 If neither ``band`` or ``afw_name`` is defined, this is used
57 as the `~lsst.afw.image.Filter` ``name``, otherwise it is added to the
58 list of `~lsst.afw.image.Filter` aliases.
59 """
61 band: Optional[str] = None
62 """The generic name of a filter not associated with a particular instrument
63 (e.g. `r` for the SDSS Gunn r-band, which could be on SDSS, LSST, or HSC).
65 Not all filters have an abstract filter: engineering or test filters may
66 not have a genericly-termed filter name.
68 If specified and if `afw_name` is None, this is used as the
69 `~lsst.afw.image.Filter` ``name`` field, otherwise it is added to the list
70 of `~lsst.afw.image.Filter` aliases.
71 """
73 doc: Optional[str] = None
74 """A short description of this filter, possibly with a link to more
75 information.
76 """
78 afw_name: Optional[str] = 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: AbstractSet[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 : `Sequence`
118 The filters in this collection.
119 """
121 physical_to_band: Dict[str, Optional[str]]
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