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

56 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2022-09-07 04:12 -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 declare ``physical_filter`` and ``band`` 

41 information for an instrument. 

42 

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

44 definition system that includes complete transmission information. 

45 """ 

46 

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. 

51 

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

53 

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

58 

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

62 

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

64 not have a genericly-termed filter name. 

65 

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

70 

71 doc: Optional[str] = None 

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

73 information. 

74 """ 

75 

76 afw_name: Optional[str] = None 

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

78 

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

84 

85 alias: AbstractSet[str] = frozenset() 

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

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

88 """ 

89 

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

94 

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

104 

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) 

108 

109 

110class FilterDefinitionCollection(Sequence[FilterDefinition]): 

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

112 

113 Parameters 

114 ---------- 

115 filters : `Sequence` 

116 The filters in this collection. 

117 """ 

118 

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

125 

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} 

129 

130 @overload 

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

132 pass 

133 

134 @overload 

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

136 pass 

137 

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

139 return self._filters[index] 

140 

141 def __len__(self) -> int: 

142 return len(self._filters) 

143 

144 def __str__(self) -> str: 

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

146 

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

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

149 

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. 

153 

154 Parameters 

155 ---------- 

156 name : `str` 

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

158 

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