Coverage for python/lsst/obs/lsst/filters.py: 99%
84 statements
« prev ^ index » next coverage.py v7.3.3, created at 2023-12-16 14:32 +0000
« prev ^ index » next coverage.py v7.3.3, created at 2023-12-16 14:32 +0000
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 "GENERIC_FILTER_DEFINITIONS",
30)
32from lsst.obs.base import FilterDefinition, FilterDefinitionCollection
33from .translators.lsst import FILTER_DELIMITER
36def addFilter(filter_dict, band, physical_filter):
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, alias=[],
46 )
49# Collection to handle the distinct cases where no filter is being used.
50NoFilterCollection = FilterDefinitionCollection(
51 # For this case, no filter is being used and the optical path to
52 # the focal plane is unoccluded.
53 FilterDefinition(physical_filter="empty", band="white",
54 alias={"no_filter", "open"}),
55 # For this case, all filters are returned to the carousel and the
56 # auto-changer partially occludes the focal plane. See Tony
57 # Johnson's comment at https://jira.lsstcorp.org/browse/DM-41675.
58 FilterDefinition(physical_filter="none", band="white",
59 alias={"no_filter", "open"}),
60)
62# Generic filters used by PhoSim and UCDCam
63LsstCamFiltersGeneric = FilterDefinitionCollection(
64 FilterDefinition(physical_filter="u", band="u"),
65 FilterDefinition(physical_filter="g", band="g"),
66 FilterDefinition(physical_filter="r", band="r"),
67 FilterDefinition(physical_filter="i", band="i"),
68 FilterDefinition(physical_filter="z", band="z"),
69 FilterDefinition(physical_filter="y", band="y"),
70)
72# The LSST Filters from Tony Johnson, 05/09/2023, in DM-38882.
73LsstCamFiltersBaseline = FilterDefinitionCollection(
74 FilterDefinition(physical_filter="ph_5", band="white"), # pinhole filter
75 FilterDefinition(physical_filter="ef_43", band="white"), # "empty" filter
76 FilterDefinition(physical_filter="u_24", band="u"),
77 FilterDefinition(physical_filter="g_6", band="g"),
78 FilterDefinition(physical_filter="r_57", band="r"),
79 FilterDefinition(physical_filter="i_39", band="i"),
80 FilterDefinition(physical_filter="z_20", band="z"),
81 FilterDefinition(physical_filter="y_10", band="y"),
82)
84#
85# Define the filters present in the BOT.
86# According to Tony Johnson the possible physical filters are
87# ColorFWheel [SDSSu,SDSSg,SDSSr,SDSSi,SDSSz,SDSSY,
88# 480nm,650nm,750nm,870nm,950nm,970nm]
89# SpotProjFWheel [grid,spot,empty3,empty4,empty5,empty6]
90# NeutralFWheel [ND_OD1.0,ND_OD0.5,ND_OD0.3,empty,ND_OD2.0,ND_OD0.7]
91# where:
92# ColorFWheel and SpotProjFWheel are mutually exclusive and
93# both appear in FILTER,
94# NeutralFWheel appears in FILTER2
95#
96# Experimentally we also see FILTER2 values of:
97# ['ND_OD0.01', 'ND_OD0.05', 'ND_OD0.4', 'ND_OD3.0', 'ND_OD4.0']
98#
99# The band names are not yet defined, so I'm going to invent them
101# Update from 2023-11-18:
102# in DM-41675 Yousuke notes the following update to the BOT filters:
103# SpotProjFWheel: [grid, spot, sparsegrid, streak, ellipses, empty6]
105# Map the BOT filters to corresponding band explicitly
106BOT_filter_map = {
107 "empty": "white",
108 "SDSSu": "u",
109 "SDSSg": "g",
110 "SDSSr": "r",
111 "SDSSi": "i",
112 "SDSSz": "z",
113 "SDSSY": "y",
114 "480nm": "g",
115 "650nm": "r",
116 "750nm": "i",
117 "870nm": "z",
118 "950nm": "y",
119 "970nm": "y",
120 "grid": "grid",
121 "spot": "spot",
122 "sparsegrid": "sparsegrid",
123 "streak": "streak",
124 "ellipses": "ellipses"
125}
127BOTFilters_dict = {}
128for physical_filter, band in BOT_filter_map.items():
129 if physical_filter == "empty":
130 pass # Already defined above
131 else:
132 addFilter(BOTFilters_dict, band, physical_filter)
134 # Empty ND is removed by metadata translator so is not needed here
135 ndFilters = ["ND_OD0.1", "ND_OD0.3", "ND_OD0.5", "ND_OD0.7", "ND_OD1.0", "ND_OD2.0"]
136 # We found these additional filters in BOT data files:
137 ndFilters += ['ND_OD0.01', 'ND_OD0.05', 'ND_OD0.4', 'ND_OD3.0', 'ND_OD4.0']
139 for nd in ndFilters:
140 # fully qualified physical filter
141 phys_plus_nd = f"{physical_filter}{FILTER_DELIMITER}{nd}"
143 # When one of the filters is empty we can just use the real filter
144 # (e.g. "u" not "u~empty"); but we always need at least one "empty"
145 if band == "white":
146 # Use the ND on its own
147 phys_plus_nd = nd
149 # Use a generic ND modifier for the band
150 ndband = f"{band}{FILTER_DELIMITER}nd"
152 addFilter(BOTFilters_dict, band=ndband, physical_filter=phys_plus_nd)
154BOTFilters = [
155 FilterDefinition(band="unknown", physical_filter="unknown"),
156]
157for band, physical_filters in BOTFilters_dict.items():
158 for physical_filter, filter_defn in physical_filters.items():
159 BOTFilters.append(FilterDefinition(**filter_defn))
161#
162# These are the filters used by the CCOB for both TS8 and LSSTCam.
163# For the testing of LSSTCam at SLAC, they will be combined with the
164# real LSSTCam filters, so we include those combinations in the CCOB
165# filter definitions.
166#
167CCOB_filter_map = {
168 "": "white",
169 "HIGH": "white",
170 "LOW": "white",
171 "uv": "u",
172 "blue": "g",
173 "red": "r",
174 "nm750": "i",
175 "nm850": "z",
176 "nm960": "y",
177}
179CCOBFilters = []
180for lsst_filter_def in (*NoFilterCollection, *LsstCamFiltersBaseline):
181 lsstcam_filter = lsst_filter_def.physical_filter
182 if lsstcam_filter == "empty":
183 lsstcam_filter = ""
184 lsstcam_band = lsst_filter_def.band
185 for ccob_filter, ccob_band in CCOB_filter_map.items():
186 if lsstcam_band != "white" and ccob_band != "white" and band != ccob_band:
187 # Skip disallowed filter combinations based on band values.
188 continue
189 if ccob_filter == "":
190 # This would correspond to an entry already in
191 # LSSTCamFilterBaseline
192 continue
193 filters = lsstcam_filter, ccob_filter
194 physical_filter = FILTER_DELIMITER.join(filters).strip(FILTER_DELIMITER)
195 if lsstcam_band == "white":
196 band = ccob_band
197 else:
198 band = lsstcam_band
199 if physical_filter == "": 199 ↛ 200line 199 didn't jump to line 200, because the condition on line 199 was never true
200 physical_filter = "empty"
201 CCOBFilters.append(FilterDefinition(band=band, physical_filter=physical_filter))
203#
204# The filters that we might see in the real LSSTCam (including in SLAC)
205#
206# Note that the filters we'll use on the sky, LsstCamFiltersBaseline, must
207# come first as we're not allocating enough bits in _computeCoaddExposureId
208# for all the BOT composite filters (i.e. "u~ND_OD1.0")
209#
210LSSTCAM_FILTER_DEFINITIONS = FilterDefinitionCollection(
211 *NoFilterCollection,
212 *LsstCamFiltersBaseline,
213 *BOTFilters,
214 *CCOBFilters,
215)
217GENERIC_FILTER_DEFINITIONS = FilterDefinitionCollection(
218 *NoFilterCollection,
219 *LsstCamFiltersGeneric,
220)
222#
223# Filters in SLAC's Test Stand 3
224#
225TS3Filters = [
226 FilterDefinition(band="unknown", physical_filter="unknown"),
227 FilterDefinition(physical_filter="275CutOn"),
228 FilterDefinition(physical_filter="550CutOn")]
230TS3_FILTER_DEFINITIONS = FilterDefinitionCollection(
231 *NoFilterCollection,
232 *LsstCamFiltersGeneric,
233 *TS3Filters,
234)
235#
236# Filters in SLAC's Test Stand 8
237#
238TS8Filters = [
239 FilterDefinition(band="unknown", physical_filter="unknown"),
240 FilterDefinition(physical_filter="275CutOn"),
241 FilterDefinition(physical_filter="550CutOn")]
243TS8_FILTER_DEFINITIONS = FilterDefinitionCollection(
244 *NoFilterCollection,
245 *LsstCamFiltersGeneric,
246 *LsstCamFiltersBaseline,
247 *TS8Filters,
248 *BOTFilters,
249 *CCOBFilters,
250)
253# LATISS filters include a grating in the name so we need to construct
254# filters for each combination of filter+grating.
256# This set of filters can be installed in either the grating or filter wheel,
257# so we define them here to avoid duplication.
258sdss65mm_filters = [FilterDefinition(physical_filter="SDSSu_65mm",
259 band="u"),
260 FilterDefinition(physical_filter="SDSSg_65mm",
261 band="g"),
262 FilterDefinition(physical_filter="SDSSr_65mm",
263 band="r"),
264 FilterDefinition(physical_filter="SDSSi_65mm",
265 band="i"),
266 FilterDefinition(physical_filter="SDSSz_65mm",
267 band="z"),
268 FilterDefinition(physical_filter="SDSSy_65mm",
269 band="y"),
270 ]
272_latiss_filters = (
273 FilterDefinition(physical_filter="empty",
274 band="white",
275 alias={"no_filter", "open"}),
276 FilterDefinition(physical_filter="blank_bk7_wg05",
277 band="white"),
278 FilterDefinition(physical_filter="KPNO_1111_436nm",
279 band="g"),
280 FilterDefinition(physical_filter="KPNO_373A_677nm",
281 band="r"),
282 FilterDefinition(physical_filter="KPNO_406_828nm",
283 band="z"),
284 FilterDefinition(physical_filter="diffuser",
285 band="diffuser"),
286 FilterDefinition(physical_filter="unknown",
287 band="unknown"),
288 FilterDefinition(physical_filter="BG40",
289 band="g",
290 afw_name="bg"),
291 FilterDefinition(physical_filter="BG40_65mm_1",
292 band="g",
293 afw_name="bg"),
294 FilterDefinition(physical_filter="BG40_65mm_2",
295 band="g",
296 afw_name="bg"),
297 FilterDefinition(physical_filter="quadnotch1",
298 band="notch"),
299 FilterDefinition(physical_filter="RG610",
300 band="r",
301 afw_name="rg"),
302 FilterDefinition(physical_filter="OG550_65mm_1",
303 band="g",
304 afw_name="bg"),
305 FilterDefinition(physical_filter="OG550_65mm_2",
306 band="g",
307 afw_name="bg"),
308 FilterDefinition(physical_filter="FELH0600",
309 band="r",
310 afw_name="rg"),
311 FilterDefinition(physical_filter="SDSSg",
312 band="g"),
313 FilterDefinition(physical_filter="SDSSr",
314 band="r"),
315 FilterDefinition(physical_filter="SDSSi",
316 band="i"),
317 FilterDefinition(physical_filter="collimator",
318 band="white"),
319 FilterDefinition(physical_filter="cyl_lens",
320 band="white"),
321 *sdss65mm_filters,
322)
324# Form a new set of filter definitions from all the explicit gratings, and add
325# sdss65mm_filter set.
326_latiss_gratings = ("ronchi90lpmm",
327 "ronchi170lpmm",
328 "empty",
329 "unknown",
330 "holo4_003",
331 "blue300lpmm_qn1",
332 "holo4_001",
333 "pinhole_1_1000",
334 "pinhole_1_0500",
335 "pinhole_1_0200",
336 "pinhole_1_0100",
337 "pinhole_2_1_1000",
338 "pinhole_2_1_0500",
339 "pinhole_2_1_0200",
340 "pinhole_2_1_0100",
341 "pinhole_2_2_1000",
342 "pinhole_2_2_0500",
343 "pinhole_2_2_0200",
344 "pinhole_2_2_0100",
345 "pinhole_2_3_1000",
346 "pinhole_2_3_0500",
347 "pinhole_2_3_0200",
348 "pinhole_2_3_0100",
349 "pinhole_2_4_1000",
350 "pinhole_2_4_0500",
351 "pinhole_2_4_0200",
352 "pinhole_2_4_0100",
353 *sdss65mm_filters,
354 )
356# Include the filters without the grating in case someone wants
357# to retrieve a filter by an actual filter name
358_latiss_filter_and_grating = [f for f in _latiss_filters]
360for filter in _latiss_filters:
361 for grating in _latiss_gratings:
362 # The diffuser "filter" was never used with gratings
363 # so skip it
364 if filter.physical_filter == "diffuser":
365 continue
367 # If the grating is a FilterDefinition, use the band and alias
368 # attributes defined in the grating for the new, combined
369 # FilterDefinition. In addition, filter out any combinations that do
370 # not use the "empty" filter in combination with a grating that is a
371 # FitlerDefintion as we should never combine multiple filters in real
372 # observations.
373 if isinstance(grating, FilterDefinition):
374 if filter.physical_filter != "empty":
375 continue
377 new_name = FILTER_DELIMITER.join([filter.physical_filter, grating.physical_filter])
378 new_aliases = {FILTER_DELIMITER.join([filter.physical_filter, a]) for a in grating.alias}
380 combo = FilterDefinition(physical_filter=new_name,
381 band=grating.band,
382 afw_name=grating.afw_name,
383 alias=new_aliases)
384 else:
385 new_name = FILTER_DELIMITER.join([filter.physical_filter, grating])
386 new_aliases = {FILTER_DELIMITER.join([a, grating]) for a in filter.alias}
388 combo = FilterDefinition(physical_filter=new_name,
389 band=filter.band,
390 afw_name=filter.afw_name,
391 alias=new_aliases)
393 _latiss_filter_and_grating.append(combo)
396LATISS_FILTER_DEFINITIONS = FilterDefinitionCollection(*_latiss_filter_and_grating)
399LSSTCAM_IMSIM_FILTER_DEFINITIONS = FilterDefinitionCollection(
400 # These were computed using throughputs 1.4 and
401 # lsst.sims.photUtils.BandpassSet.
402 FilterDefinition(physical_filter="u_sim_1.4",
403 band="u"),
404 FilterDefinition(physical_filter="g_sim_1.4",
405 band="g"),
406 FilterDefinition(physical_filter="r_sim_1.4",
407 band="r"),
408 FilterDefinition(physical_filter="i_sim_1.4",
409 band="i"),
410 FilterDefinition(physical_filter="z_sim_1.4",
411 band="z"),
412 FilterDefinition(physical_filter="y_sim_1.4",
413 band="y"),
414 *LsstCamFiltersGeneric,
415)
417# ###########################################################################
418#
419# ComCam
420#
421# See https://jira.lsstcorp.org/browse/DM-21706
423ComCamFilters_dict = {}
424for band, sn in [("u", "SN-05"), # incorrect sub thickness
425 ("u", "SN-02"), # not yet coated
426 ("u", "SN-06"), # not yet coated
427 ("g", "SN-07"), # bad cosmetics
428 ("g", "SN-01"),
429 ("r", "SN-03"),
430 ("i", "SN-06"),
431 ("z", "SN-03"),
432 ("z", "SN-02"), # failed specs
433 ("y", "SN-04"),
434 ]:
435 physical_filter = f"{band}_{sn[3:]}"
436 lsstCamFilter = [f for f in LsstCamFiltersBaseline if f.band == band][0]
438 addFilter(ComCamFilters_dict, band, physical_filter)
441ComCamFilters = [
442 FilterDefinition(band="white", physical_filter="empty"),
443 FilterDefinition(band="unknown", physical_filter="unknown"),
444]
445for band, physical_filters in ComCamFilters_dict.items():
446 for physical_filter, filter_defn in physical_filters.items():
447 ComCamFilters.append(FilterDefinition(**filter_defn))
449COMCAM_FILTER_DEFINITIONS = FilterDefinitionCollection(
450 *ComCamFilters,
451)