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 if band not in filter_dict:
40 filter_dict[band] = dict(physical_filter=physical_filter,
41 lambdaEff=lambdaEff, alias=[])
42 else:
43 assert filter_dict[band]["lambdaEff"] == lambdaEff
45 filter_dict[band]["alias"].append(physical_filter)
48# The LSST Filters from L. Jones 05/14/2020 - "Edges" = 5% of peak throughput
49# See https://github.com/rhiannonlynne/notebooks/blob/master/Filter%20Characteristics.ipynb # noqa: W505
50#
51# N.b. DM-26623 requests that these physical names be updated once
52# the camera team has decided upon the final values (CAP-617)
54LsstCamFiltersBaseline = FilterDefinitionCollection(
55 FilterDefinition(physical_filter="empty", band="white",
56 lambdaEff=0.0,
57 alias={"no_filter", "open"}),
58 FilterDefinition(physical_filter="u", band="u",
59 lambdaEff=368.48, lambdaMin=320.00, lambdaMax=408.60),
60 FilterDefinition(physical_filter="g", band="g",
61 lambdaEff=480.20, lambdaMin=386.40, lambdaMax=567.00),
62 FilterDefinition(physical_filter="r", band="r",
63 lambdaEff=623.12, lambdaMin=537.00, lambdaMax=706.00),
64 FilterDefinition(physical_filter="i", band="i",
65 lambdaEff=754.17, lambdaMin=676.00, lambdaMax=833.00),
66 FilterDefinition(physical_filter="z", band="z",
67 lambdaEff=869.05, lambdaMin=803.00, lambdaMax=938.60),
68 FilterDefinition(physical_filter="y", band="y",
69 lambdaEff=973.64, lambdaMin=908.40, lambdaMax=1099.00),
70)
72#
73# Define the filters present in the BOT.
74# According to Tony Johnson the possible physical filters are
75# ColorFWheel [SDSSu,SDSSg,SDSSr,SDSSi,SDSSz,SDSSY,
76# 480nm,650nm,750nm,870nm,950nm,970nm]
77# SpotProjFWheel [grid,spot,empty3,empty4,empty5,empty6]
78# NeutralFWheel [ND_OD1.0,ND_OD0.5,ND_OD0.3,empty,ND_OD2.0,ND_OD0.7]
79# where:
80# ColorFWheel and SpotProjFWheel are mutually exclusive and
81# both appear in FILTER,
82# NeutralFWheel appears in FILTER2
83#
84# Experimentally we also see FILTER2 values of:
85# ['ND_OD0.01', 'ND_OD0.05', 'ND_OD0.4', 'ND_OD3.0', 'ND_OD4.0']
86#
87# The band names are not yet defined, so I'm going to invent them
90BOTFilters_dict = {}
91for physical_filter in [
92 "empty",
93 "SDSSu",
94 "SDSSg",
95 "SDSSr",
96 "SDSSi",
97 "SDSSz",
98 "SDSSY",
99 "480nm",
100 "650nm",
101 "750nm",
102 "870nm",
103 "950nm",
104 "970nm",
105 "grid",
106 "spot",
107 "empty3",
108 "empty4",
109 "empty5",
110 "empty6",
111]:
112 mat = re.search(r"^SDSS(.)$", physical_filter)
113 if mat:
114 band = mat.group(1).lower()
116 lsstCamFilter = [f for f in LsstCamFiltersBaseline if f.band == band][0]
117 lambdaEff = lsstCamFilter.lambdaEff
118 else:
119 if re.search(r"^empty[3-6]$", physical_filter):
120 band = "white"
121 else:
122 band = physical_filter
123 lambdaEff = 0.0
125 if physical_filter == "empty":
126 pass # already in LsstCamFiltersBaseline
127 else:
128 addFilter(BOTFilters_dict, band, physical_filter, lambdaEff=lambdaEff)
130 ndFilters = ["empty", "ND_OD0.1", "ND_OD0.3", "ND_OD0.5", "ND_OD0.7", "ND_OD1.0", "ND_OD2.0"]
131 # We found these additional filters in BOT data files:
132 ndFilters += ['ND_OD0.01', 'ND_OD0.05', 'ND_OD0.4', 'ND_OD3.0', 'ND_OD4.0']
134 for nd in ndFilters:
135 pf = f"{physical_filter}{FILTER_DELIMITER}{nd}" # fully qualified physical filter
137 # When one of the filters is empty we can just use the real filter
138 # (e.g. "u" not "u~empty"); but we always need at least one "empty"
139 #
140 # Don't use . in band names, it's just asking for trouble
141 # if they ever end up in filenames
142 if nd == "empty":
143 if band == "white":
144 af = "white"
145 else:
146 af = f"{band}"
147 elif band == "white":
148 pf = nd
149 af = f"{nd.replace('.', '_')}"
150 else:
151 af = f"{band}{FILTER_DELIMITER}{nd.replace('.', '_')}"
153 addFilter(BOTFilters_dict, band=af, physical_filter=pf, lambdaEff=lambdaEff)
155BOTFilters = [
156 FilterDefinition(band="unknown", physical_filter="unknown", lambdaEff=0.0),
157]
158for band, filt in BOTFilters_dict.items():
159 BOTFilters.append(FilterDefinition(band=band,
160 physical_filter=filt["physical_filter"],
161 lambdaEff=filt["lambdaEff"],
162 alias=filt["alias"]))
163#
164# The filters that we might see in the real LSSTCam (including in SLAC)
165#
166# Note that the filters we'll use on the sky, LsstCamFiltersBaseline, must
167# come first as we're not allocating enough bits in _computeCoaddExposureId
168# for all the BOT composite filters (i.e. "u~ND_OD1.0")
169#
170LSSTCAM_FILTER_DEFINITIONS = FilterDefinitionCollection(
171 *LsstCamFiltersBaseline,
172 *BOTFilters,
173)
175#
176# Filters in SLAC's Test Stand 3
177#
178TS3Filters = [
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)]
183TS3_FILTER_DEFINITIONS = FilterDefinitionCollection(
184 *LsstCamFiltersBaseline,
185 *TS3Filters,
186)
187#
188# Filters in SLAC's Test Stand 8
189#
190TS8Filters = [
191 FilterDefinition(band="unknown", physical_filter="unknown", lambdaEff=0.0),
192 FilterDefinition(physical_filter="275CutOn", lambdaEff=0.0),
193 FilterDefinition(physical_filter="550CutOn", lambdaEff=0.0)]
195TS8_FILTER_DEFINITIONS = FilterDefinitionCollection(
196 *LsstCamFiltersBaseline,
197 *TS8Filters,
198)
201# LATISS filters include a grating in the name so we need to construct
202# filters for each combination of filter+grating.
203_latiss_filters = (
204 FilterDefinition(physical_filter="empty",
205 lambdaEff=0.0,
206 alias={"no_filter", "open"}),
207 FilterDefinition(physical_filter="blank_bk7_wg05",
208 lambdaEff=0.0),
209 FilterDefinition(physical_filter="KPNO_1111_436nm",
210 band="g",
211 lambdaEff=436.0, lambdaMin=386.0, lambdaMax=486.0),
212 FilterDefinition(physical_filter="KPNO_373A_677nm",
213 band="r",
214 lambdaEff=677.0, lambdaMin=624.0, lambdaMax=730.0),
215 FilterDefinition(physical_filter="KPNO_406_828nm",
216 band="z",
217 lambdaEff=828.0, lambdaMin=738.5, lambdaMax=917.5),
218 FilterDefinition(physical_filter="diffuser",
219 lambdaEff=0.0),
220 FilterDefinition(physical_filter="empty",
221 lambdaEff=0.0),
222 FilterDefinition(physical_filter="unknown",
223 lambdaEff=0.0),
224 FilterDefinition(physical_filter="BG40",
225 # band="g", # afw only allows one g filter
226 lambdaEff=472.0, lambdaMin=334.5, lambdaMax=609.5),
227 FilterDefinition(physical_filter="quadnotch1",
228 lambdaEff=0.0),
229 FilterDefinition(physical_filter="RG610",
230 lambdaEff=0.0),
231)
233# Form a new set of filter definitions from all the explicit filters
234_latiss_gratings = ("ronchi90lpmm", "ronchi170lpmm", "empty", "none", "unknown")
236# Include the filters without the grating in case someone wants
237# to retrieve a filter by an actual filter name
238_latiss_filter_and_grating = [f for f in _latiss_filters]
240for filter in _latiss_filters:
241 for grating in _latiss_gratings:
242 # FilterDefinition is a frozen dataclass
243 new_name = FILTER_DELIMITER.join([filter.physical_filter, grating])
245 # Also need to update aliases
246 new_aliases = {FILTER_DELIMITER.join([a, grating]) for a in filter.alias}
248 combo = FilterDefinition(physical_filter=new_name,
249 lambdaEff=filter.lambdaEff,
250 lambdaMin=filter.lambdaMin,
251 lambdaMax=filter.lambdaMax,
252 alias=new_aliases)
253 _latiss_filter_and_grating.append(combo)
256LATISS_FILTER_DEFINITIONS = FilterDefinitionCollection(*_latiss_filter_and_grating)
259LSSTCAM_IMSIM_FILTER_DEFINITIONS = FilterDefinitionCollection(
260 # These were computed using throughputs 1.4 and
261 # lsst.sims.photUtils.BandpassSet.
262 FilterDefinition(physical_filter="u_sim_1.4",
263 band="u",
264 lambdaEff=367.070, lambdaMin=308.0, lambdaMax=408.6),
265 FilterDefinition(physical_filter="g_sim_1.4",
266 band="g",
267 lambdaEff=482.685, lambdaMin=386.5, lambdaMax=567.0),
268 FilterDefinition(physical_filter="r_sim_1.4",
269 band="r",
270 lambdaEff=622.324, lambdaMin=537.0, lambdaMax=706.0),
271 FilterDefinition(physical_filter="i_sim_1.4",
272 band="i",
273 lambdaEff=754.598, lambdaMin=676.0, lambdaMax=833.0),
274 FilterDefinition(physical_filter="z_sim_1.4",
275 band="z",
276 lambdaEff=869.090, lambdaMin=803.0, lambdaMax=938.6),
277 FilterDefinition(physical_filter="y_sim_1.4",
278 band="y",
279 lambdaEff=971.028, lambdaMin=908.4, lambdaMax=1096.3)
280)
282# ###########################################################################
283#
284# ComCam
285#
286# See https://jira.lsstcorp.org/browse/DM-21706
288ComCamFilters_dict = {}
289for band, sn in [("u", "SN-05"), # incorrect sub thickness
290 ("u", "SN-02"), # not yet coated
291 ("u", "SN-06"), # not yet coated
292 ("g", "SN-07"), # bad cosmetics
293 ("g", "SN-01"),
294 ("r", "SN-03"),
295 ("i", "SN-06"),
296 ("z", "SN-03"),
297 ("z", "SN-02"), # failed specs
298 ("y", "SN-04"),
299 ]:
300 physical_filter = f"{band}_{sn[3:]}"
301 lsstCamFilter = [f for f in LsstCamFiltersBaseline if f.band == band][0]
302 lambdaEff = lsstCamFilter.lambdaEff
304 addFilter(ComCamFilters_dict, band, physical_filter, lambdaEff=lambdaEff)
307ComCamFilters = [
308 FilterDefinition(band="white", physical_filter="empty", lambdaEff=0.0),
309 FilterDefinition(band="unknown", physical_filter="unknown", lambdaEff=0.0),
310]
311for band, filt in ComCamFilters_dict.items():
312 ComCamFilters.append(FilterDefinition(band=band,
313 physical_filter=filt["physical_filter"],
314 lambdaEff=filt["lambdaEff"],
315 alias=filt["alias"]))
317COMCAM_FILTER_DEFINITIONS = FilterDefinitionCollection(
318 *ComCamFilters,
319)