Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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 

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

27 

28import dataclasses 

29import collections.abc 

30 

31import numpy as np 

32 

33import lsst.afw.image.utils 

34 

35 

36class FilterDefinitionCollection(collections.abc.Sequence): 

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

38 

39 Parameters 

40 ---------- 

41 filters : `~collections.abc.Sequence` 

42 The filters in this collection. 

43 """ 

44 

45 _defined = None 

46 """Whether these filters have been defined via 

47 `~lsst.afw.image.utils.defineFilter`. If so, set to ``self`` to identify 

48 the filter collection that defined them. 

49 """ 

50 

51 def __init__(self, *filters): 

52 self._filters = list(filters) 

53 

54 def __getitem__(self, key): 

55 return self._filters[key] 

56 

57 def __len__(self): 

58 return len(self._filters) 

59 

60 def __str__(self): 

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

62 

63 def defineFilters(self): 

64 """Define all the filters to `lsst.afw.image.Filter`. 

65 

66 `~lsst.afw.image.Filter` objects are singletons, so we protect against 

67 filters being defined multiple times. 

68 

69 Raises 

70 ------ 

71 RuntimeError 

72 Raised if any other `FilterDefinitionCollection` has already called 

73 ``defineFilters``. 

74 """ 

75 if self._defined is None: 75 ↛ 80line 75 didn't jump to line 80, because the condition on line 75 was never false

76 self.reset() 

77 for filter in self._filters: 77 ↛ 78line 77 didn't jump to line 78, because the loop on line 77 never started

78 filter.defineFilter() 

79 FilterDefinitionCollection._defined = self 

80 elif self._defined is self: 

81 # noop: we've already defined these filters, so do nothing 

82 pass 

83 else: 

84 msg = f"afw Filters were already defined on: {self._defined}" 

85 raise RuntimeError(msg) 

86 

87 @classmethod 

88 def reset(cls): 

89 """Reset the afw Filter definitions and clear the `defined` singleton. 

90 Use this in unittests that define different filters. 

91 """ 

92 lsst.afw.image.utils.resetFilters() 

93 cls._defined = None 

94 

95 

96@dataclasses.dataclass(frozen=True) 

97class FilterDefinition: 

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

99 

100 This class is used to interface between the `~lsst.afw.image.Filter` class 

101 and the Gen2 `~lsst.daf.persistence.CameraMapper` and Gen3 

102 `~lsst.obs.base.Instruments` and ``physical_filter``/``abstract_filter`` 

103 `~lsst.daf.butler.Dimension`. 

104 

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

106 definition system that includes complete transmission information. 

107 """ 

108 

109 physical_filter: str 

110 """The name of a filter associated with a particular instrument: unique for 

111 each piece of glass. This should match the exact filter name used in the 

112 observatory's metadata. 

113 

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

115 

116 If neither ``abstract_filter`` or ``afw_name`` is defined, this is used 

117 as the `~lsst.afw.image.Filter` ``name``, otherwise it is added to the 

118 list of `~lsst.afw.image.Filter` aliases. 

119 """ 

120 

121 lambdaEff: float 

122 """The effective wavelength of this filter (nm).""" 

123 

124 abstract_filter: str = None 

125 """The generic name of a filter not associated with a particular instrument 

126 (e.g. `r` for the SDSS Gunn r-band, which could be on SDSS, LSST, or HSC). 

127 

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

129 not have a genericly-termed filter name. 

130 

131 If specified and if `afw_name` is None, this is used as the 

132 `~lsst.afw.image.Filter` ``name`` field, otherwise it is added to the list 

133 of `~lsst.afw.image.Filter` aliases. 

134 """ 

135 

136 afw_name: str = None 

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

138 

139 This is distinct from physical_filter and abstract_filter to maintain 

140 backwards compatibility in some obs packages. 

141 For example, for HSC there are two distinct ``r`` and ``i`` filters, named 

142 ``r/r2`` and ``i/i2``. 

143 """ 

144 

145 lambdaMin: float = np.nan 

146 """The minimum wavelength of this filter (nm; defined as 1% throughput)""" 

147 lambdaMax: float = np.nan 

148 """The maximum wavelength of this filter (nm; defined as 1% throughput)""" 

149 

150 alias: set = frozenset() 

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

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

153 """ 

154 

155 def __post_init__(self): 

156 # force alias to be immutable, so that hashing works 

157 if not isinstance(self.alias, frozenset): 

158 object.__setattr__(self, 'alias', frozenset(self.alias)) 

159 

160 def __str__(self): 

161 txt = f"FilterDefinition(physical_filter='{self.physical_filter}', lambdaEff='{self.lambdaEff}'" 

162 if self.abstract_filter is not None: 

163 txt += f", abstract_filter='{self.abstract_filter}'" 

164 if self.afw_name is not None: 

165 txt += f", afw_name='{self.afw_name}'" 

166 if not np.isnan(self.lambdaMin): 

167 txt += f", lambdaMin='{self.lambdaMin}'" 

168 if not np.isnan(self.lambdaMax): 

169 txt += f", lambdaMax='{self.lambdaMax}'" 

170 if len(self.alias) != 0: 

171 txt += f", alias='{self.alias}'" 

172 return txt + ")" 

173 

174 def defineFilter(self): 

175 """Declare the filters via afw.image.Filter. 

176 """ 

177 aliases = set(self.alias) 

178 name = self.physical_filter 

179 if self.abstract_filter is not None: 

180 name = self.abstract_filter 

181 aliases.add(self.physical_filter) 

182 if self.afw_name is not None: 

183 name = self.afw_name 

184 aliases.add(self.physical_filter) 

185 # Only add `physical_filter/abstract_filter` as an alias if afw_name is defined.ee 

186 if self.afw_name is not None: 

187 if self.abstract_filter is not None: 

188 aliases.add(self.abstract_filter) 

189 lsst.afw.image.utils.defineFilter(name, 

190 lambdaEff=self.lambdaEff, 

191 lambdaMin=self.lambdaMin, 

192 lambdaMax=self.lambdaMax, 

193 alias=sorted(aliases))