lsst.obs.base  21.0.0-13-ge9a044d+c2b517099a
filters.py
Go to the documentation of this file.
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
23 and for use by `lsst.afw.image.Filter`, gen2 dataIds, and gen3 Dimensions.
24 """
25 
26 __all__ = ("FilterDefinition", "FilterDefinitionCollection")
27 
28 import dataclasses
29 import collections.abc
30 
31 import numpy as np
32 
33 import lsst.afw.image.utils
34 
35 
36 class 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:
76  self.reset()
77  for filter in self._filters:
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  def findAll(self, name):
96  """Return the FilterDefinitions that match a particular name.
97 
98  This method makes no attempt to prioritize, e.g., band names over
99  physical filter names; any definition that makes *any* reference
100  to the name is returned.
101 
102  Parameters
103  ----------
104  name : `str`
105  The name to search for. May be any band, physical, or alias name.
106 
107  Returns
108  -------
109  matches : `set` [`FilterDefinition`]
110  All FilterDefinitions containing ``name`` as one of their
111  filter names.
112  """
113  matches = set()
114  for filter in self._filters:
115  if name == filter.physical_filter or name == filter.band or name == filter.afw_name \
116  or name in filter.alias:
117  matches.add(filter)
118  return matches
119 
120 
121 @dataclasses.dataclass(frozen=True)
123  """The definition of an instrument's filter bandpass.
124 
125  This class is used to interface between the `~lsst.afw.image.Filter` class
126  and the Gen2 `~lsst.daf.persistence.CameraMapper` and Gen3
127  `~lsst.obs.base.Instruments` and ``physical_filter``/``band``
128  `~lsst.daf.butler.Dimension`.
129 
130  This class is likely temporary, until we have a better versioned filter
131  definition system that includes complete transmission information.
132  """
133 
134  physical_filter: str
135  """The name of a filter associated with a particular instrument: unique for
136  each piece of glass. This should match the exact filter name used in the
137  observatory's metadata.
138 
139  This name is used to define the ``physical_filter`` gen3 Butler Dimension.
140 
141  If neither ``band`` or ``afw_name`` is defined, this is used
142  as the `~lsst.afw.image.Filter` ``name``, otherwise it is added to the
143  list of `~lsst.afw.image.Filter` aliases.
144  """
145 
146  lambdaEff: float
147  """The effective wavelength of this filter (nm)."""
148 
149  band: str = None
150  """The generic name of a filter not associated with a particular instrument
151  (e.g. `r` for the SDSS Gunn r-band, which could be on SDSS, LSST, or HSC).
152 
153  Not all filters have an abstract filter: engineering or test filters may
154  not have a genericly-termed filter name.
155 
156  If specified and if `afw_name` is None, this is used as the
157  `~lsst.afw.image.Filter` ``name`` field, otherwise it is added to the list
158  of `~lsst.afw.image.Filter` aliases.
159  """
160 
161  afw_name: str = None
162  """If not None, the name of the `~lsst.afw.image.Filter` object.
163 
164  This is distinct from physical_filter and band to maintain
165  backwards compatibility in some obs packages.
166  For example, for HSC there are two distinct ``r`` and ``i`` filters, named
167  ``r/r2`` and ``i/i2``.
168  """
169 
170  lambdaMin: float = np.nan
171  """The minimum wavelength of this filter (nm; defined as 1% throughput)"""
172  lambdaMax: float = np.nan
173  """The maximum wavelength of this filter (nm; defined as 1% throughput)"""
174 
175  alias: set = frozenset()
176  """Alternate names for this filter. These are added to the
177  `~lsst.afw.image.Filter` alias list.
178  """
179 
180  def __post_init__(self):
181  # force alias to be immutable, so that hashing works
182  if not isinstance(self.alias, frozenset):
183  object.__setattr__(self, 'alias', frozenset(self.alias))
184 
185  def __str__(self):
186  txt = f"FilterDefinition(physical_filter='{self.physical_filter}', lambdaEff='{self.lambdaEff}'"
187  if self.band is not None:
188  txt += f", band='{self.band}'"
189  if self.afw_name is not None:
190  txt += f", afw_name='{self.afw_name}'"
191  if not np.isnan(self.lambdaMin):
192  txt += f", lambdaMin='{self.lambdaMin}'"
193  if not np.isnan(self.lambdaMax):
194  txt += f", lambdaMax='{self.lambdaMax}'"
195  if len(self.alias) != 0:
196  txt += f", alias='{self.alias}'"
197  return txt + ")"
198 
199  def defineFilter(self):
200  """Declare the filters via afw.image.Filter.
201  """
202  aliases = set(self.alias)
203  name = self.physical_filter
204  if self.band is not None:
205  name = self.band
206  aliases.add(self.physical_filter)
207  if self.afw_name is not None:
208  name = self.afw_name
209  aliases.add(self.physical_filter)
210  # Only add `physical_filter/band` as an alias if afw_name is defined.ee
211  if self.afw_name is not None:
212  if self.band is not None:
213  aliases.add(self.band)
214  lsst.afw.image.utils.defineFilter(name,
215  lambdaEff=self.lambdaEff,
216  lambdaMin=self.lambdaMin,
217  lambdaMax=self.lambdaMax,
218  alias=sorted(aliases))
219 
220  def makeFilterLabel(self):
221  """Create a complete FilterLabel for this filter.
222  """
223  return lsst.afw.image.FilterLabel(band=self.band, physical=self.physical_filter)
lsst.obs.base.filters.FilterDefinitionCollection.defineFilters
def defineFilters(self)
Definition: filters.py:63
lsst.obs.base.filters.FilterDefinition.makeFilterLabel
def makeFilterLabel(self)
Definition: filters.py:220
lsst.obs.base.filters.FilterDefinitionCollection._defined
_defined
Definition: filters.py:45
lsst.obs.base.filters.FilterDefinitionCollection.__getitem__
def __getitem__(self, key)
Definition: filters.py:54
lsst.obs.base.filters.FilterDefinitionCollection.__str__
def __str__(self)
Definition: filters.py:60
lsst.obs.base.filters.FilterDefinitionCollection.__len__
def __len__(self)
Definition: filters.py:57
lsst.obs.base.filters.FilterDefinitionCollection.__init__
def __init__(self, *filters)
Definition: filters.py:51
lsst.obs.base.filters.FilterDefinition.set
set
Definition: filters.py:175
lsst.obs.base.filters.FilterDefinition.defineFilter
def defineFilter(self)
Definition: filters.py:199
lsst.obs.base.filters.FilterDefinitionCollection
Definition: filters.py:36
lsst.obs.base.filters.FilterDefinition.__post_init__
def __post_init__(self)
Definition: filters.py:180
lsst.obs.base.filters.FilterDefinitionCollection.reset
def reset(cls)
Definition: filters.py:88
lsst.obs.base.filters.FilterDefinition.__str__
def __str__(self)
Definition: filters.py:185
lsst.obs.base.filters.FilterDefinitionCollection._filters
_filters
Definition: filters.py:52
lsst.obs.base.filters.FilterDefinition
Definition: filters.py:122
lsst.obs.base.filters.FilterDefinitionCollection.findAll
def findAll(self, name)
Definition: filters.py:95