Coverage for python/lsst/obs/base/filters.py: 55%

56 statements  

« prev     ^ index     » next       coverage.py v7.2.5, created at 2023-05-02 18:43 -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/>. 

21 

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""" 

25 

26from __future__ import annotations 

27 

28__all__ = ("FilterDefinition", "FilterDefinitionCollection") 

29 

30import dataclasses 

31from typing import AbstractSet, Any, Dict, Optional, Sequence, Set, overload 

32 

33import lsst.afw.image.utils 

34 

35 

36@dataclasses.dataclass(frozen=True) 

37class FilterDefinition: 

38 """The definition of an instrument's filter bandpass. 

39 

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`. 

44 

45 This class is likely temporary, until we have a better versioned filter 

46 definition system that includes complete transmission information. 

47 """ 

48 

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. 

53 

54 This name is used to define the ``physical_filter`` gen3 Butler Dimension. 

55 

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 """ 

60 

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). 

64 

65 Not all filters have an abstract filter: engineering or test filters may 

66 not have a genericly-termed filter name. 

67 

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 """ 

72 

73 doc: Optional[str] = None 

74 """A short description of this filter, possibly with a link to more 

75 information. 

76 """ 

77 

78 afw_name: Optional[str] = None 

79 """If not None, the name of the `~lsst.afw.image.Filter` object. 

80 

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 """ 

86 

87 alias: AbstractSet[str] = frozenset() 

88 """Alternate names for this filter. These are added to the 

89 `~lsst.afw.image.Filter` alias list. 

90 """ 

91 

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)) 

96 

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 + ")" 

106 

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) 

110 

111 

112class FilterDefinitionCollection(Sequence[FilterDefinition]): 

113 """An order-preserving collection of multiple `FilterDefinition`. 

114 

115 Parameters 

116 ---------- 

117 filters : `Sequence` 

118 The filters in this collection. 

119 """ 

120 

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 """ 

127 

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} 

131 

132 @overload 

133 def __getitem__(self, i: int) -> FilterDefinition: 

134 pass 

135 

136 @overload 

137 def __getitem__(self, s: slice) -> Sequence[FilterDefinition]: 

138 pass 

139 

140 def __getitem__(self, index: Any) -> Any: 

141 return self._filters[index] 

142 

143 def __len__(self) -> int: 

144 return len(self._filters) 

145 

146 def __str__(self) -> str: 

147 return "FilterDefinitions(" + ", ".join(str(f) for f in self._filters) + ")" 

148 

149 def findAll(self, name: str) -> Set[FilterDefinition]: 

150 """Return the FilterDefinitions that match a particular name. 

151 

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. 

155 

156 Parameters 

157 ---------- 

158 name : `str` 

159 The name to search for. May be any band, physical, or alias name. 

160 

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