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

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

# This file is part of obs_base. 

# 

# Developed for the LSST Data Management System. 

# This product includes software developed by the LSST Project 

# (https://www.lsst.org). 

# See the COPYRIGHT file at the top-level directory of this distribution 

# for details of code ownership. 

# 

# This program is free software: you can redistribute it and/or modify 

# it under the terms of the GNU General Public License as published by 

# the Free Software Foundation, either version 3 of the License, or 

# (at your option) any later version. 

# 

# This program is distributed in the hope that it will be useful, 

# but WITHOUT ANY WARRANTY; without even the implied warranty of 

# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

# GNU General Public License for more details. 

# 

# You should have received a copy of the GNU General Public License 

# along with this program. If not, see <https://www.gnu.org/licenses/>. 

 

"""Classes to allow obs packages to define the filters used by an Instrument 

and for use by `lsst.afw.image.Filter`, gen2 dataIds, and gen3 Dimensions. 

""" 

 

__all__ = ("FilterDefinition", "FilterDefinitionCollection") 

 

import dataclasses 

import collections.abc 

 

import numpy as np 

 

import lsst.afw.image.utils 

 

 

class FilterDefinitionCollection(collections.abc.Sequence): 

"""An order-preserving collection of `FilterDefinition`s. 

 

Parameters 

---------- 

filters : sequence 

The filters in this collection. 

""" 

 

_defined = None 

"""Whether these filters have been defined via 

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

the filter collection that defined them. 

""" 

 

def __init__(self, *filters): 

self._filters = list(filters) 

 

def __getitem__(self, key): 

return self._filters[key] 

 

def __len__(self): 

return len(self._filters) 

 

def __str__(self): 

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

 

def defineFilters(self): 

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

 

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

filters being defined multiple times. 

 

Raises 

------ 

RuntimeError 

Raised if any other `FilterDefinitionCollection` has already called 

``defineFilters``. 

""" 

if self._defined is None: 

self.reset() 

for filter in self._filters: 

filter.defineFilter() 

FilterDefinitionCollection._defined = self 

elif self._defined is self: 

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

pass 

else: 

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

raise RuntimeError(msg) 

 

@classmethod 

def reset(cls): 

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

Use this in unittests that define different filters. 

""" 

lsst.afw.image.utils.resetFilters() 

cls._defined = None 

 

 

@dataclasses.dataclass(frozen=True) 

class FilterDefinition: 

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

 

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

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

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

`~lsst.daf.butler.Dimension`. 

 

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

definition system that includes complete transmission information. 

""" 

 

physical_filter: str 

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

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

observatory's metadata. 

 

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

 

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

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

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

""" 

 

lambdaEff: float 

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

 

abstract_filter: str = None 

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

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

 

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

not have a genericly-termed filter name. 

 

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

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

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

""" 

 

afw_name: str = None 

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

 

This is distinct from physical_filter and abstract_filter to maintain 

backwards compatibility in some obs packages. 

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

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

""" 

 

lambdaMin: float = np.nan 

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

lambdaMax: float = np.nan 

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

 

alias: set = frozenset() 

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

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

""" 

 

def __post_init__(self): 

# force alias to be immutable, so that hashing works 

if not isinstance(self.alias, frozenset): 

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

 

def __str__(self): 

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

if self.abstract_filter is not None: 

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

if self.afw_name is not None: 

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

if not np.isnan(self.lambdaMin): 

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

if not np.isnan(self.lambdaMax): 

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

if len(self.alias) != 0: 

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

return txt + ")" 

 

def defineFilter(self): 

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

""" 

aliases = set(self.alias) 

name = self.physical_filter 

if self.abstract_filter is not None: 

name = self.abstract_filter 

aliases.add(self.physical_filter) 

if self.afw_name is not None: 

name = self.afw_name 

aliases.add(self.physical_filter) 

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

if self.afw_name is not None: 

if self.abstract_filter is not None: 

aliases.add(self.abstract_filter) 

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

lambdaEff=self.lambdaEff, 

lambdaMin=self.lambdaMin, 

lambdaMax=self.lambdaMax, 

alias=sorted(aliases))