Coverage for python/lsst/obs/lsst/filters.py : 100%

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_lsst.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (http://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 <http://www.gnu.org/licenses/>.
22__all__ = (
23 "LSSTCAM_FILTER_DEFINITIONS",
24 "LATISS_FILTER_DEFINITIONS",
25 "LSSTCAM_IMSIM_FILTER_DEFINITIONS",
26 "TS3_FILTER_DEFINITIONS",
27 "TS8_FILTER_DEFINITIONS",
28 "COMCAM_FILTER_DEFINITIONS",
29)
31import re
32from lsst.obs.base import FilterDefinition, FilterDefinitionCollection
33from .translators.lsst import FILTER_DELIMITER
36def addFilter(filter_dict, band, physical_filter, lambdaEff=0.0):
37 """Define a filter in filter_dict, to be converted to a Filter later"""
39 # index by band but keep distinct physical filters by band
40 # since there can be many physical filters for a single band
41 if band not in filter_dict:
42 filter_dict[band] = {}
44 filter_dict[band][physical_filter] = dict(physical_filter=physical_filter,
45 band=band, lambdaEff=lambdaEff, alias=[],
46 )
49# The LSST Filters from L. Jones 05/14/2020 - "Edges" = 5% of peak throughput
50# See https://github.com/rhiannonlynne/notebooks/blob/master/Filter%20Characteristics.ipynb # noqa: W505
51#
52# N.b. DM-26623 requests that these physical names be updated once
53# the camera team has decided upon the final values (CAP-617)
55LsstCamFiltersBaseline = FilterDefinitionCollection(
56 FilterDefinition(physical_filter="empty", band="white",
57 lambdaEff=0.0,
58 alias={"no_filter", "open"}),
59 FilterDefinition(physical_filter="u", band="u",
60 lambdaEff=368.48, lambdaMin=320.00, lambdaMax=408.60),
61 FilterDefinition(physical_filter="g", band="g",
62 lambdaEff=480.20, lambdaMin=386.40, lambdaMax=567.00),
63 FilterDefinition(physical_filter="r", band="r",
64 lambdaEff=623.12, lambdaMin=537.00, lambdaMax=706.00),
65 FilterDefinition(physical_filter="i", band="i",
66 lambdaEff=754.17, lambdaMin=676.00, lambdaMax=833.00),
67 FilterDefinition(physical_filter="z", band="z",
68 lambdaEff=869.05, lambdaMin=803.00, lambdaMax=938.60),
69 FilterDefinition(physical_filter="y", band="y",
70 lambdaEff=973.64, lambdaMin=908.40, lambdaMax=1099.00),
71)
73#
74# Define the filters present in the BOT.
75# According to Tony Johnson the possible physical filters are
76# ColorFWheel [SDSSu,SDSSg,SDSSr,SDSSi,SDSSz,SDSSY,
77# 480nm,650nm,750nm,870nm,950nm,970nm]
78# SpotProjFWheel [grid,spot,empty3,empty4,empty5,empty6]
79# NeutralFWheel [ND_OD1.0,ND_OD0.5,ND_OD0.3,empty,ND_OD2.0,ND_OD0.7]
80# where:
81# ColorFWheel and SpotProjFWheel are mutually exclusive and
82# both appear in FILTER,
83# NeutralFWheel appears in FILTER2
84#
85# Experimentally we also see FILTER2 values of:
86# ['ND_OD0.01', 'ND_OD0.05', 'ND_OD0.4', 'ND_OD3.0', 'ND_OD4.0']
87#
88# The band names are not yet defined, so I'm going to invent them
90# Map the BOT filters to corresponding band explicitly
91BOT_filter_map = {
92 "empty": "white",
93 "SDSSu": "u",
94 "SDSSg": "g",
95 "SDSSr": "r",
96 "SDSSi": "i",
97 "SDSSz": "z",
98 "SDSSY": "y",
99 "480nm": "g",
100 "650nm": "r",
101 "750nm": "i",
102 "870nm": "z",
103 "950nm": "y",
104 "970nm": "y",
105 "grid": "grid",
106 "spot": "spot",
107}
109BOTFilters_dict = {}
110for physical_filter, band in BOT_filter_map.items():
111 lambdaEff = 0.0
112 lsstCamFilterMatches = [f for f in LsstCamFiltersBaseline if f.band == band]
113 if lsstCamFilterMatches:
114 lambdaEff = lsstCamFilterMatches[0].lambdaEff
116 if mat := re.match(r"(\d+)nm$", physical_filter):
117 lambdaEff = float(mat.group(1))
119 if physical_filter == "empty":
120 pass # Already defined above
121 else:
122 addFilter(BOTFilters_dict, band, physical_filter, lambdaEff=lambdaEff)
124 # Empty ND is removed by metadata translator so is not needed here
125 ndFilters = ["ND_OD0.1", "ND_OD0.3", "ND_OD0.5", "ND_OD0.7", "ND_OD1.0", "ND_OD2.0"]
126 # We found these additional filters in BOT data files:
127 ndFilters += ['ND_OD0.01', 'ND_OD0.05', 'ND_OD0.4', 'ND_OD3.0', 'ND_OD4.0']
129 for nd in ndFilters:
130 # fully qualified physical filter
131 phys_plus_nd = f"{physical_filter}{FILTER_DELIMITER}{nd}"
133 # When one of the filters is empty we can just use the real filter
134 # (e.g. "u" not "u~empty"); but we always need at least one "empty"
135 if band == "white":
136 # Use the ND on its own
137 phys_plus_nd = nd
139 # Use a generic ND modifier for the band
140 ndband = f"{band}{FILTER_DELIMITER}nd"
142 addFilter(BOTFilters_dict, band=ndband, physical_filter=phys_plus_nd, lambdaEff=lambdaEff)
144BOTFilters = [
145 FilterDefinition(band="unknown", physical_filter="unknown", lambdaEff=0.0),
146]
147for band, physical_filters in BOTFilters_dict.items():
148 for physical_filter, filter_defn in physical_filters.items():
149 BOTFilters.append(FilterDefinition(**filter_defn))
151#
152# The filters that we might see in the real LSSTCam (including in SLAC)
153#
154# Note that the filters we'll use on the sky, LsstCamFiltersBaseline, must
155# come first as we're not allocating enough bits in _computeCoaddExposureId
156# for all the BOT composite filters (i.e. "u~ND_OD1.0")
157#
158LSSTCAM_FILTER_DEFINITIONS = FilterDefinitionCollection(
159 *LsstCamFiltersBaseline,
160 *BOTFilters,
161)
163#
164# Filters in SLAC's Test Stand 3
165#
166TS3Filters = [
167 FilterDefinition(band="unknown", physical_filter="unknown", lambdaEff=0.0),
168 FilterDefinition(physical_filter="275CutOn", lambdaEff=0.0),
169 FilterDefinition(physical_filter="550CutOn", lambdaEff=0.0)]
171TS3_FILTER_DEFINITIONS = FilterDefinitionCollection(
172 *LsstCamFiltersBaseline,
173 *TS3Filters,
174)
175#
176# Filters in SLAC's Test Stand 8
177#
178TS8Filters = [
179 FilterDefinition(band="unknown", physical_filter="unknown", lambdaEff=0.0),
180 FilterDefinition(physical_filter="275CutOn", lambdaEff=0.0),
181 FilterDefinition(physical_filter="550CutOn", lambdaEff=0.0)]
183TS8_FILTER_DEFINITIONS = FilterDefinitionCollection(
184 *LsstCamFiltersBaseline,
185 *TS8Filters,
186)
189# LATISS filters include a grating in the name so we need to construct
190# filters for each combination of filter+grating.
191_latiss_filters = (
192 FilterDefinition(physical_filter="empty",
193 band="white",
194 lambdaEff=0.0,
195 alias={"no_filter", "open"}),
196 FilterDefinition(physical_filter="blank_bk7_wg05",
197 band="white",
198 lambdaEff=0.0),
199 FilterDefinition(physical_filter="KPNO_1111_436nm",
200 band="g",
201 lambdaEff=436.0, lambdaMin=386.0, lambdaMax=486.0),
202 FilterDefinition(physical_filter="KPNO_373A_677nm",
203 band="r",
204 lambdaEff=677.0, lambdaMin=624.0, lambdaMax=730.0),
205 FilterDefinition(physical_filter="KPNO_406_828nm",
206 band="z",
207 lambdaEff=828.0, lambdaMin=738.5, lambdaMax=917.5),
208 FilterDefinition(physical_filter="diffuser",
209 band="diffuser",
210 lambdaEff=0.0),
211 FilterDefinition(physical_filter="unknown",
212 band="unknown",
213 lambdaEff=0.0),
214 FilterDefinition(physical_filter="BG40",
215 band="g",
216 afw_name="bg",
217 lambdaEff=472.0, lambdaMin=334.5, lambdaMax=609.5),
218 FilterDefinition(physical_filter="quadnotch1",
219 lambdaEff=0.0,
220 band="notch"),
221 FilterDefinition(physical_filter="RG610",
222 lambdaEff=0.0,
223 band="r",
224 afw_name="rg"),
225)
227# Form a new set of filter definitions from all the explicit gratings
228_latiss_gratings = ("ronchi90lpmm", "ronchi170lpmm", "empty", "unknown", "holo4_003")
230# Include the filters without the grating in case someone wants
231# to retrieve a filter by an actual filter name
232_latiss_filter_and_grating = [f for f in _latiss_filters]
234for filter in _latiss_filters:
235 for grating in _latiss_gratings:
236 # The diffuser "filter" was never used with gratings
237 # so skip it
238 if filter.physical_filter == "diffuser":
239 continue
241 # FilterDefinition is a frozen dataclass
242 new_name = FILTER_DELIMITER.join([filter.physical_filter, grating])
244 # Also need to update aliases
245 new_aliases = {FILTER_DELIMITER.join([a, grating]) for a in filter.alias}
247 # For gratings set the band to the band of the filter
248 combo = FilterDefinition(physical_filter=new_name,
249 band=filter.band,
250 lambdaEff=filter.lambdaEff,
251 lambdaMin=filter.lambdaMin,
252 lambdaMax=filter.lambdaMax,
253 afw_name=filter.afw_name,
254 alias=new_aliases)
255 _latiss_filter_and_grating.append(combo)
258LATISS_FILTER_DEFINITIONS = FilterDefinitionCollection(*_latiss_filter_and_grating)
261LSSTCAM_IMSIM_FILTER_DEFINITIONS = FilterDefinitionCollection(
262 # These were computed using throughputs 1.4 and
263 # lsst.sims.photUtils.BandpassSet.
264 FilterDefinition(physical_filter="u_sim_1.4",
265 band="u",
266 lambdaEff=367.070, lambdaMin=308.0, lambdaMax=408.6),
267 FilterDefinition(physical_filter="g_sim_1.4",
268 band="g",
269 lambdaEff=482.685, lambdaMin=386.5, lambdaMax=567.0),
270 FilterDefinition(physical_filter="r_sim_1.4",
271 band="r",
272 lambdaEff=622.324, lambdaMin=537.0, lambdaMax=706.0),
273 FilterDefinition(physical_filter="i_sim_1.4",
274 band="i",
275 lambdaEff=754.598, lambdaMin=676.0, lambdaMax=833.0),
276 FilterDefinition(physical_filter="z_sim_1.4",
277 band="z",
278 lambdaEff=869.090, lambdaMin=803.0, lambdaMax=938.6),
279 FilterDefinition(physical_filter="y_sim_1.4",
280 band="y",
281 lambdaEff=971.028, lambdaMin=908.4, lambdaMax=1096.3)
282)
284# ###########################################################################
285#
286# ComCam
287#
288# See https://jira.lsstcorp.org/browse/DM-21706
290ComCamFilters_dict = {}
291for band, sn in [("u", "SN-05"), # incorrect sub thickness
292 ("u", "SN-02"), # not yet coated
293 ("u", "SN-06"), # not yet coated
294 ("g", "SN-07"), # bad cosmetics
295 ("g", "SN-01"),
296 ("r", "SN-03"),
297 ("i", "SN-06"),
298 ("z", "SN-03"),
299 ("z", "SN-02"), # failed specs
300 ("y", "SN-04"),
301 ]:
302 physical_filter = f"{band}_{sn[3:]}"
303 lsstCamFilter = [f for f in LsstCamFiltersBaseline if f.band == band][0]
304 lambdaEff = lsstCamFilter.lambdaEff
306 addFilter(ComCamFilters_dict, band, physical_filter, lambdaEff=lambdaEff)
309ComCamFilters = [
310 FilterDefinition(band="white", physical_filter="empty", lambdaEff=0.0),
311 FilterDefinition(band="unknown", physical_filter="unknown", lambdaEff=0.0),
312]
313for band, physical_filters in ComCamFilters_dict.items():
314 for physical_filter, filter_defn in physical_filters.items():
315 ComCamFilters.append(FilterDefinition(**filter_defn))
317COMCAM_FILTER_DEFINITIONS = FilterDefinitionCollection(
318 *ComCamFilters,
319)