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