Coverage for python/lsst/obs/hsc/makeTransmissionCurves.py: 21%
58 statements
« prev ^ index » next coverage.py v6.4.1, created at 2022-06-07 02:29 -0700
« prev ^ index » next coverage.py v6.4.1, created at 2022-06-07 02:29 -0700
1#!/usr/bin/env python
2#
3# LSST Data Management System
4#
5# Copyright 2018 AURA/LSST.
6#
7# This product includes software developed by the
8# LSST Project (http://www.lsst.org/).
9#
10# This program is free software: you can redistribute it and/or modify
11# it under the terms of the GNU General Public License as published by
12# the Free Software Foundation, either version 3 of the License, or
13# (at your option) any later version.
14#
15# This program is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the LSST License Statement and
21# the GNU General Public License along with this program. If not,
22# see <https://www.lsstcorp.org/LegalNotices/>.
23#
24import os
25import glob
26import numpy as np
28from lsst.afw.image import TransmissionCurve
29from lsst.utils import getPackageDir
31__all__ = ("getOpticsTransmission", "getSensorTransmission", "getAtmosphereTransmission",
32 "getFilterTransmission",)
34DATA_DIR = os.path.join(getPackageDir("obs_subaru"), "hsc", "transmission")
36HSC_BEGIN = "2012-12-18" # initial date for curves valid for entire lifetime of HSC
38# This maps the old names used in afw.image.Filter and various NAOJ-side things
39# to the physical_filter names we've now standardized on. It includes only
40# broad-band filters; the narrow-band filters are easier to transform without
41# an explicit mapping (see getPhysicalFilterName).
42OLD_FILTER_NAME_MAP = {b: f"HSC-{b.upper()}" for b in "grizy"}
43OLD_FILTER_NAME_MAP.update(r2="HSC-R2", i2="HSC-I2")
46def getPhysicalFilterName(short):
47 """Return an HSC physical_filter name (e.g. 'HSC-R') that's usable as a
48 data ID value from various names used in the transmission data files.
49 """
50 if short.startswith("HSC"):
51 return short
52 # Sometimes narrow-band and intermediate-band filters start with NB or IB,
53 # and we don't want to try to enumerate all of those if we can avoid it.
54 if short.startswith("NB") or short.startswith("IB"):
55 num = int(short[2:].lstrip("0"))
56 return "%s%04d" % (short[:2], num)
57 # Sometimes the narrow-band filters start with just N instead.
58 if short.startswith("N"):
59 num = int(short[1:].lstrip("0"))
60 return "NB%04d" % (num,)
61 return OLD_FILTER_NAME_MAP.get(short, short)
64def readTransmissionCurveFromFile(filename, unit="angstrom", atMin=None, atMax=None):
65 """Load a spatial TransmissionCurve from a text file with wavelengths and
66 throughputs in columns.
68 Parameters
69 ----------
70 filename : `str`
71 Name of the file to read.
72 unit : `str`
73 Wavelength unit; one of "nm" or "angstrom".
74 atMin : `float`
75 Throughput to use at wavelengths below the tabulated minimum. If
76 ``None``, the tabulated throughput at the mininum will be used.
77 atMax : `float`
78 Throughput to use at wavelengths above the tabulated maximum. If
79 ``None``, the tabulated throughput at the maximum will be used.
80 """
81 wavelengths, throughput = np.loadtxt(os.path.join(DATA_DIR, filename), usecols=[0, 1], unpack=True)
82 i = np.argsort(wavelengths)
83 wavelengths = wavelengths[i]
84 throughput = throughput[i]
85 if unit == "nm":
86 wavelengths *= 10
87 elif unit != "angstrom":
88 raise ValueError("Invalid wavelength unit")
89 if atMin is None:
90 atMin = throughput[0]
91 if atMax is None:
92 atMax = throughput[-1]
93 return TransmissionCurve.makeSpatiallyConstant(throughput=throughput, wavelengths=wavelengths,
94 throughputAtMin=atMin, throughputAtMax=atMax)
97def getOpticsTransmission():
98 """Return a dictionary of TransmissionCurves describing the combined
99 throughput of HSC and the Subaru primary mirror.
101 Dictionary keys are string dates (YYYY-MM-DD) indicating the beginning of
102 the validity period for the curve stored as the associated dictionary
103 value. If the curve is spatially varying, it will be defined in focal
104 plane coordinates.
106 Dictionary values may be None to indicate that no TransmissionCurve is
107 valid after the date provided in the key.
108 """
109 mirror2010 = readTransmissionCurveFromFile("M1-2010s.txt", unit="nm")
110 camera = readTransmissionCurveFromFile("throughput_popt2.txt")
111 camera *= readTransmissionCurveFromFile("throughput_win.txt")
112 return {
113 HSC_BEGIN: mirror2010*camera,
114 "2017-10-01": None # mirror recoating begins, approximately
115 }
118def getSensorTransmission():
119 """Return a nested dictionary of TransmissionCurves describing the
120 throughput of each sensor.
122 Outer directionary keys are string dates (YYYY-MM-DD), with values
123 a dictionary mapping CCD ID to TransmissionCurve. If the curve
124 is spatially varying, it will be defined in pixel coordinates.
126 Outer dictionary values may be None to indicate that no TransmissionCurve
127 is valid after the date provided in the key.
128 """
129 qe = readTransmissionCurveFromFile("qe_ccd_HSC.txt", atMin=0.0, atMax=0.0)
130 return {HSC_BEGIN: {n: qe for n in range(112)}}
133def getAtmosphereTransmission():
134 """Return a dictionary of TransmissionCurves describing the atmospheric
135 throughput at Mauna Kea.
137 Dictionary keys are string dates (YYYY-MM-DD) indicating the beginning of
138 the validity period for the curve stored as the associated dictionary
139 value. The curve is guaranteed not to be spatially-varying.
141 Dictionary values may be None to indicate that no TransmissionCurve is
142 valid after the date provided in the key.
143 """
144 average = readTransmissionCurveFromFile("modtran_maunakea_am12_pwv15_binned10ang.dat")
145 return {HSC_BEGIN: average}
148def getFilterTransmission():
149 """Return a nested dictionary of TransmissionCurves describing the
150 throughput of each HSC filter.
152 Outer directionary keys are string dates (YYYY-MM-DD), with values
153 a dictionary mapping filter name to TransmissionCurve. If the curve
154 is spatially varying, it will be defined in pixel coordinates.
156 Filter curve names are in the long form used as data ID values (e.g.
157 'HSC-I').
159 Outer dictionary values may be None to indicate that no TransmissionCurve
160 is valid after the date provided in the key.
161 """
162 module = {}
163 filename = os.path.join(DATA_DIR, "filterTraces.py")
164 with open(filename) as file:
165 exec(compile(file.read(), filename, mode='exec'), module)
166 result = {}
167 for band, data in module["FILTER_DATA"].items():
168 result[getPhysicalFilterName(band)] = TransmissionCurve.makeRadial(
169 throughput=data["T"], wavelengths=data["lam"]*10,
170 radii=data['radius']/module["PIXEL_SIZE"],
171 throughputAtMin=0.0, throughputAtMax=0.0
172 )
173 for filename in glob.glob(os.path.join(DATA_DIR, "wHSC-*.txt")):
174 band = getPhysicalFilterName(os.path.split(filename)[1][len("wHSC-"): -len(".txt")])
175 if band not in result:
176 result[band] = readTransmissionCurveFromFile(filename, atMin=0.0, atMax=0.0)
177 return {HSC_BEGIN: result}