lsst.obs.base  21.0.0-24-g1074344+7a8564be62
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  physical_to_band = {}
52  """A mapping from physical filter name to band name.
53  This is a convenience feature to allow file readers to create a FilterLabel
54  when reading a raw file that only has a physical filter name, without
55  iterating over the entire collection.
56  """
57 
58  def __init__(self, *filters):
59  self._filters_filters = list(filters)
60  self.physical_to_bandphysical_to_bandphysical_to_band = {filter.physical_filter: filter.band for filter in self._filters_filters}
61 
62  def __getitem__(self, key):
63  return self._filters_filters[key]
64 
65  def __len__(self):
66  return len(self._filters_filters)
67 
68  def __str__(self):
69  return "FilterDefinitions(" + ', '.join(str(f) for f in self._filters_filters) + ')'
70 
71  def defineFilters(self):
72  """Define all the filters to `lsst.afw.image.Filter`.
73 
74  `~lsst.afw.image.Filter` objects are singletons, so we protect against
75  filters being defined multiple times.
76 
77  Raises
78  ------
79  RuntimeError
80  Raised if any other `FilterDefinitionCollection` has already called
81  ``defineFilters``.
82  """
83  if self._defined_defined is None:
84  self.resetreset()
85  for filter in self._filters_filters:
86  filter.defineFilter()
87  FilterDefinitionCollection._defined = self
88  elif self._defined_defined is self:
89  # noop: we've already defined these filters, so do nothing
90  pass
91  else:
92  msg = f"afw Filters were already defined on: {self._defined}"
93  raise RuntimeError(msg)
94 
95  @classmethod
96  def reset(cls):
97  """Reset the afw Filter definitions and clear the `defined` singleton.
98  Use this in unittests that define different filters.
99  """
100  lsst.afw.image.utils.resetFilters()
101  cls._defined_defined = None
102 
103  def findAll(self, name):
104  """Return the FilterDefinitions that match a particular name.
105 
106  This method makes no attempt to prioritize, e.g., band names over
107  physical filter names; any definition that makes *any* reference
108  to the name is returned.
109 
110  Parameters
111  ----------
112  name : `str`
113  The name to search for. May be any band, physical, or alias name.
114 
115  Returns
116  -------
117  matches : `set` [`FilterDefinition`]
118  All FilterDefinitions containing ``name`` as one of their
119  filter names.
120  """
121  matches = set()
122  for filter in self._filters_filters:
123  if name == filter.physical_filter or name == filter.band or name == filter.afw_name \
124  or name in filter.alias:
125  matches.add(filter)
126  return matches
127 
128 
129 @dataclasses.dataclass(frozen=True)
131  """The definition of an instrument's filter bandpass.
132 
133  This class is used to interface between the `~lsst.afw.image.Filter` class
134  and the Gen2 `~lsst.daf.persistence.CameraMapper` and Gen3
135  `~lsst.obs.base.Instruments` and ``physical_filter``/``band``
136  `~lsst.daf.butler.Dimension`.
137 
138  This class is likely temporary, until we have a better versioned filter
139  definition system that includes complete transmission information.
140  """
141 
142  physical_filter: str
143  """The name of a filter associated with a particular instrument: unique for
144  each piece of glass. This should match the exact filter name used in the
145  observatory's metadata.
146 
147  This name is used to define the ``physical_filter`` gen3 Butler Dimension.
148 
149  If neither ``band`` or ``afw_name`` is defined, this is used
150  as the `~lsst.afw.image.Filter` ``name``, otherwise it is added to the
151  list of `~lsst.afw.image.Filter` aliases.
152  """
153 
154  lambdaEff: float
155  """The effective wavelength of this filter (nm)."""
156 
157  band: str = None
158  """The generic name of a filter not associated with a particular instrument
159  (e.g. `r` for the SDSS Gunn r-band, which could be on SDSS, LSST, or HSC).
160 
161  Not all filters have an abstract filter: engineering or test filters may
162  not have a genericly-termed filter name.
163 
164  If specified and if `afw_name` is None, this is used as the
165  `~lsst.afw.image.Filter` ``name`` field, otherwise it is added to the list
166  of `~lsst.afw.image.Filter` aliases.
167  """
168 
169  afw_name: str = None
170  """If not None, the name of the `~lsst.afw.image.Filter` object.
171 
172  This is distinct from physical_filter and band to maintain
173  backwards compatibility in some obs packages.
174  For example, for HSC there are two distinct ``r`` and ``i`` filters, named
175  ``r/r2`` and ``i/i2``.
176  """
177 
178  lambdaMin: float = np.nan
179  """The minimum wavelength of this filter (nm; defined as 1% throughput)"""
180  lambdaMax: float = np.nan
181  """The maximum wavelength of this filter (nm; defined as 1% throughput)"""
182 
183  alias: set = frozenset()
184  """Alternate names for this filter. These are added to the
185  `~lsst.afw.image.Filter` alias list.
186  """
187 
188  def __post_init__(self):
189  # force alias to be immutable, so that hashing works
190  if not isinstance(self.alias, frozenset):
191  object.__setattr__(self, 'alias', frozenset(self.alias))
192 
193  def __str__(self):
194  txt = f"FilterDefinition(physical_filter='{self.physical_filter}', lambdaEff='{self.lambdaEff}'"
195  if self.band is not None:
196  txt += f", band='{self.band}'"
197  if self.afw_name is not None:
198  txt += f", afw_name='{self.afw_name}'"
199  if not np.isnan(self.lambdaMin):
200  txt += f", lambdaMin='{self.lambdaMin}'"
201  if not np.isnan(self.lambdaMax):
202  txt += f", lambdaMax='{self.lambdaMax}'"
203  if len(self.alias) != 0:
204  txt += f", alias='{self.alias}'"
205  return txt + ")"
206 
207  def defineFilter(self):
208  """Declare the filters via afw.image.Filter.
209  """
210  aliases = set(self.alias)
211  name = self.physical_filter
212  if self.band is not None:
213  name = self.band
214  aliases.add(self.physical_filter)
215  if self.afw_name is not None:
216  name = self.afw_name
217  aliases.add(self.physical_filter)
218  # Only add `physical_filter/band` as an alias if afw_name is defined.ee
219  if self.afw_name is not None:
220  if self.band is not None:
221  aliases.add(self.band)
222  lsst.afw.image.utils.defineFilter(name,
223  lambdaEff=self.lambdaEff,
224  lambdaMin=self.lambdaMin,
225  lambdaMax=self.lambdaMax,
226  alias=sorted(aliases))
227 
228  def makeFilterLabel(self):
229  """Create a complete FilterLabel for this filter.
230  """
231  return lsst.afw.image.FilterLabel(band=self.band, physical=self.physical_filter)