Coverage for python/lsst/sims/photUtils/BandpassDict.py : 16%

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
1from builtins import zip
2from builtins import object
3import copy
4import numpy
5import os
6from lsst.utils import getPackageDir
7from collections import OrderedDict
8from .Bandpass import Bandpass
9from .Sed import Sed
11__all__ = ["BandpassDict"]
13class BandpassDict(object):
14 """
15 This class will wrap an OrderedDict of Bandpass instantiations.
17 Upon instantiation, this class's constructor will resample
18 the input Bandpasses to be on the same wavelength grid (defined
19 by the first input Bandpass). The constructor will then calculate
20 the 2-D phiArray for quick calculation of magnitudes in all
21 Bandpasses simultaneously (see the member methods magListForSed,
22 magListForSedList, fluxListForSed, fluxListForSedList).
24 Note: when re-sampling the wavelength grid, it is assumed that
25 the first bandpass is sampled on a uniform grid (i.e. all bandpasses
26 are resampled to a grid with wavlen_min, wavelen_max determined by
27 the bounds of the first bandpasses grid and with wavelen_step defined
28 to be the difference between the 0th and 1st element of the first
29 bandpass' wavelength grid).
31 The class methods loadBandpassesFromFiles and loadTotalBandpassesFromFiles
32 can be used to easily read throughput files in from disk and conver them
33 into BandpassDict objects.
34 """
36 def __init__(self, bandpassList, bandpassNameList):
37 """
38 @param [in] bandpassList is a list of Bandpass instantiations
40 @param [in] bandpassNameList is a list of tags to be associated
41 with those Bandpasses. These will be used as keys for the BandpassDict.
42 """
43 self._bandpassDict = OrderedDict()
44 self._wavelen_match = None
45 for bandpassName, bandpass in zip(bandpassNameList, bandpassList):
47 if bandpassName in self._bandpassDict:
48 raise RuntimeError("The bandpass %s occurs twice in your input " % bandpassName \
49 + "to BandpassDict")
51 self._bandpassDict[bandpassName] = copy.deepcopy(bandpass)
52 if self._wavelen_match is None:
53 self._wavelen_match = self._bandpassDict[bandpassName].wavelen
55 dummySed = Sed()
56 self._phiArray, self._wavelenStep = dummySed.setupPhiArray(list(self._bandpassDict.values()))
59 def __getitem__(self, bandpass):
60 return self._bandpassDict[bandpass]
63 def __len__(self):
64 return len(self._bandpassDict)
67 def __iter__(self):
68 for val in self._bandpassDict:
69 yield val
72 def values(self):
73 """
74 Returns a list of the BandpassDict's values.
75 """
76 return list(self._bandpassDict.values())
79 def keys(self):
80 """
81 Returns a list of the BandpassDict's keys.
82 """
83 return list(self._bandpassDict.keys())
86 @classmethod
87 def loadBandpassesFromFiles(cls,
88 bandpassNames=['u', 'g', 'r', 'i', 'z', 'y'],
89 filedir = os.path.join(getPackageDir('throughputs'), 'baseline'),
90 bandpassRoot = 'filter_',
91 componentList = ['detector.dat', 'm1.dat', 'm2.dat', 'm3.dat',
92 'lens1.dat', 'lens2.dat', 'lens3.dat'],
93 atmoTransmission=os.path.join(getPackageDir('throughputs'),
94 'baseline','atmos_std.dat')):
95 """
96 Load bandpass information from files into BandpassDicts.
97 This method will separate the bandpasses into contributions due to instrumentations
98 and contributions due to the atmosphere.
100 @param [in] bandpassNames is a list of strings labeling the bandpasses
101 (e.g. ['u', 'g', 'r', 'i', 'z', 'y'])
103 @param [in] filedir is a string indicating the name of the directory containing the
104 bandpass files
106 @param [in] bandpassRoot is the root of the names of the files associated with the
107 bandpasses. This method assumes that bandpasses are stored in
108 filedir/bandpassRoot_bandpassNames[i].dat
110 @param [in] componentList lists the files associated with bandpasses representing
111 hardware components shared by all filters
112 (defaults to ['detector.dat', 'm1.dat', 'm2.dat', 'm3.dat', 'lens1.dat',
113 'lense2.dat', 'lenst3.dat']
114 for LSST). These files are also expected to be stored in filedir
116 @param [in] atmoTransmission is the absolute path to the file representing the
117 transmissivity of the atmosphere (defaults to baseline/atmos_std.dat in the LSST
118 'throughputs' package).
120 @param [out] bandpassDict is a BandpassDict containing the total
121 throughput (instrumentation + atmosphere)
123 @param [out] hardwareBandpassDict is a BandpassDict containing
124 the throughput due to instrumentation only
125 """
127 commonComponents = []
128 for cc in componentList:
129 commonComponents.append(os.path.join(filedir,cc))
131 bandpassList = []
132 hardwareBandpassList = []
134 for w in bandpassNames:
135 components = commonComponents + [os.path.join(filedir,"%s.dat" % (bandpassRoot +w))]
136 bandpassDummy = Bandpass()
137 bandpassDummy.readThroughputList(components)
138 hardwareBandpassList.append(bandpassDummy)
140 components += [atmoTransmission]
141 bandpassDummy = Bandpass()
142 bandpassDummy.readThroughputList(components)
143 bandpassList.append(bandpassDummy)
146 bandpassDict = cls(bandpassList, bandpassNames)
147 hardwareBandpassDict = cls(hardwareBandpassList, bandpassNames)
149 return bandpassDict, hardwareBandpassDict
152 @classmethod
153 def loadTotalBandpassesFromFiles(cls,
154 bandpassNames=['u', 'g', 'r', 'i', 'z', 'y'],
155 bandpassDir = os.path.join(getPackageDir('throughputs'),'baseline'),
156 bandpassRoot = 'total_'):
157 """
158 This will take the list of band passes named by bandpassNames and load them into
159 a BandpassDict
161 The bandpasses loaded this way are total bandpasses: they account for instrumental
162 and atmospheric transmission.
164 @param [in] bandpassNames is a list of names identifying each filter.
165 Defaults to ['u', 'g', 'r', 'i', 'z', 'y']
167 @param [in] bandpassDir is the name of the directory where the bandpass files are stored
169 @param [in] bandpassRoot contains the first part of the bandpass file name, i.e., it is assumed
170 that the bandpasses are stored in files of the type
172 bandpassDir/bandpassRoot_bandpassNames[i].dat
174 if we want to load bandpasses for a telescope other than LSST, we would do so
175 by altering bandpassDir and bandpassRoot
177 @param [out] bandpassDict is a BandpassDict containing the loaded throughputs
178 """
180 bandpassList = []
182 for w in bandpassNames:
183 bandpassDummy = Bandpass()
184 bandpassDummy.readThroughput(os.path.join(bandpassDir,"%s.dat" % (bandpassRoot + w)))
185 bandpassList.append(bandpassDummy)
187 return cls(bandpassList, bandpassNames)
190 def _magListForSed(self, sedobj, indices=None):
191 """
192 This is a private method which will take an sedobj which has already
193 been resampled to self._wavelen_match and calculate the magnitudes
194 of that object in each of the bandpasses stored in this Dict.
196 The results are returned as a list.
197 """
199 if sedobj.wavelen is None:
200 return [numpy.NaN]*len(self._bandpassDict)
201 else:
203 #for some reason, moving this call to flambdaTofnu()
204 #to a point earlier in the
205 #process results in some SEDs having 'None' for fnu.
206 #
207 #I looked more carefully at the documentation in Sed.py
208 #Any time you update flambda in any way, fnu gets set to 'None'
209 #This is to prevent the two arrays from getting out synch
210 #(e.g. renormalizing flambda but forgettint to renormalize fnu)
211 #
212 sedobj.flambdaTofnu()
214 if indices is not None:
215 outputList = [numpy.NaN] * len(self._bandpassDict)
216 magList = sedobj.manyMagCalc(self._phiArray, self._wavelenStep, observedBandpassInd=indices)
217 for i, ix in enumerate(indices):
218 outputList[ix] = magList[i]
219 else:
220 outputList = sedobj.manyMagCalc(self._phiArray, self._wavelenStep)
222 return outputList
225 def magListForSed(self, sedobj, indices=None):
226 """
227 Return a list of magnitudes for a single Sed object.
229 @param [in] sedobj is an Sed object. Its wavelength grid can be arbitrary. If necessary,
230 a copy will be created and resampled onto the wavelength grid of the Bandpasses before
231 magnitudes are calculated. The original Sed will be unchanged.
233 @param [in] indices is an optional list of indices indicating which bandpasses to actually
234 calculate magnitudes for. Other magnitudes will be listed as numpy.NaN (i.e. this method will
235 return as many magnitudes as were loaded with the loadBandpassesFromFiles methods; it will
236 just return numpy.NaN for magnitudes you did not actually ask for)
238 @param [out] magList is a list of magnitudes in the bandpasses stored in this BandpassDict
239 """
241 if sedobj.wavelen is not None:
243 # If the Sed's wavelength grid agrees with self._wavelen_match to one part in
244 # 10^6, just use the Sed as-is. Otherwise, copy it and resample it onto
245 # self._wavelen_match
246 if sedobj._needResample(wavelen_match=self._wavelen_match):
247 dummySed = Sed(wavelen=sedobj.wavelen, flambda=sedobj.flambda)
248 dummySed.resampleSED(force=True, wavelen_match=self._wavelen_match)
249 else:
250 dummySed = sedobj
252 return numpy.array(self._magListForSed(dummySed, indices=indices))
254 else:
255 return numpy.array([numpy.NaN]*len(self._bandpassDict))
258 def magDictForSed(self, sedobj, indices=None):
259 """
260 Return an OrderedDict of magnitudes for a single Sed object.
262 The OrderedDict will be keyed off of the keys to this BandpassDict
264 @param [in] sedobj is an Sed object. Its wavelength grid can be arbitrary. If necessary,
265 a copy will be created and resampled onto the wavelength grid of the Bandpasses before
266 magnitudes are calculated. The original Sed will be unchanged.
268 @param [in] indices is an optional list of indices indicating which bandpasses to actually
269 calculate magnitudes for. Other magnitudes will be listed as numpy.NaN (i.e. this method will
270 return as many magnitudes as were loaded with the loadBandpassesFromFiles methods; it will
271 just return numpy.NaN for magnitudes you did not actually ask for)
273 @param [out] magDict is an OrderedDict of magnitudes in the bandpasses stored in this BandpassDict
274 """
276 magList = self.magListForSed(sedobj, indices=indices)
278 outputDict = OrderedDict()
280 for ix, bp in enumerate(self._bandpassDict.keys()):
281 outputDict[bp] = magList[ix]
283 return outputDict
286 def magListForSedList(self, sedList, indices=None):
287 """
288 Return a 2-D array of magnitudes from a SedList.
289 Each row will correspond to a different Sed, each column
290 will correspond to a different bandpass, i.e. in the case of
292 mag = myBandpassDict.magListForSedList(mySedList)
294 mag[0][0] will be the magnitude of the 0th Sed in the 0th bandpass
295 mag[0][1] will be the magnitude of the 0th Sed in the 1st bandpass
296 mag[1][1] will be the magnitude of the 1st Sed in the 1st bandpass
297 etc.
299 For maximum efficiency, use the wavelenMatch keyword when loading
300 SEDs into your SedList and make sure that wavelenMatch = myBandpassDict.wavelenMatch.
301 That way, this method will not have to waste time resampling the Seds
302 onto the wavelength grid of the BandpassDict.
304 @param [in] sedList is a SedList containing the Seds
305 whose magnitudes are desired.
307 @param [in] indices is an optional list of indices indicating which bandpasses to actually
308 calculate magnitudes for. Other magnitudes will be listed as numpy.NaN (i.e. this method will
309 return as many magnitudes as were loaded with the loadBandpassesFromFiles methods; it will
310 just return numpy.NaN for magnitudes you did not actually ask for)
312 @param [out] output_list is a 2-D numpy array containing the magnitudes
313 of each Sed (the rows) in each bandpass contained in this BandpassDict
314 (the columns)
315 """
317 one_at_a_time = False
318 if sedList.wavelenMatch is None:
319 one_at_a_time = True
320 elif sedList[0]._needResample(wavelen_match=self._wavelen_match):
321 one_at_a_time = True
323 output_list = []
324 if one_at_a_time:
325 for sed_obj in sedList:
326 sub_list = self.magListForSed(sed_obj, indices=indices)
327 output_list.append(sub_list)
328 else:
329 # the difference between this block and the block above is that the block
330 # above performs the additional check of making sure that sed_obj.wavelen
331 # is equivalent to self._wavelen_match
332 for sed_obj in sedList:
333 sub_list = self._magListForSed(sed_obj, indices=indices)
334 output_list.append(sub_list)
336 return numpy.array(output_list)
339 def magArrayForSedList(self, sedList, indices=None):
340 """
341 Return a dtyped numpy array of magnitudes from a SedList.
342 The array will be keyed to the keys of this BandpassDict,
343 i.e. in the case of
345 mag = myBandpassDict.magArrayForSedList(mySedList)
347 mag['u'][0] will be the magnitude of the 0th Sed in the 'u' bandpass
348 mag['u'][1] will be the magnitude of the 1st Sed in the 'u' bandpass
349 mag['z'] will be a numpy array of every Sed's magnitude in the 'z' bandpass
350 etc.
352 For maximum efficiency, use the wavelenMatch keyword when loading
353 SEDs into your SedList and make sure that wavelenMatch = myBandpassDict.wavelenMatch.
354 That way, this method will not have to waste time resampling the Seds
355 onto the wavelength grid of the BandpassDict.
357 @param [in] sedList is a SedList containing the Seds
358 whose magnitudes are desired.
360 @param [in] indices is an optional list of indices indicating which bandpasses to actually
361 calculate magnitudes for. Other magnitudes will be listed as numpy.NaN (i.e. this method will
362 return as many magnitudes as were loaded with the loadBandpassesFromFiles methods; it will
363 just return numpy.NaN for magnitudes you did not actually ask for)
365 @param [out] output_array is a dtyped numpy array of magnitudes (see above).
366 """
368 magList = self.magListForSedList(sedList, indices=indices)
370 dtype = numpy.dtype([(bp, numpy.float) for bp in self._bandpassDict.keys()])
372 outputArray = numpy.array([tuple(row) for row in magList], dtype=dtype)
374 return outputArray
377 def _fluxListForSed(self, sedobj, indices=None):
378 """
379 This is a private method which will take an sedobj which has already
380 been resampled to self._wavelen_match and calculate the fluxes
381 of that object in each of the bandpasses stored in this Dict.
383 The results are returned as a list.
384 """
386 if sedobj.wavelen is None:
387 return [numpy.NaN]*len(self._bandpassDict)
388 else:
390 #for some reason, moving this call to flambdaTofnu()
391 #to a point earlier in the
392 #process results in some SEDs having 'None' for fnu.
393 #
394 #I looked more carefully at the documentation in Sed.py
395 #Any time you update flambda in any way, fnu gets set to 'None'
396 #This is to prevent the two arrays from getting out synch
397 #(e.g. renormalizing flambda but forgettint to renormalize fnu)
398 #
399 sedobj.flambdaTofnu()
401 if indices is not None:
402 outputList = [numpy.NaN] * len(self._bandpassDict)
403 magList = sedobj.manyFluxCalc(self._phiArray, self._wavelenStep, observedBandpassInd=indices)
404 for i, ix in enumerate(indices):
405 outputList[ix] = magList[i]
406 else:
407 outputList = sedobj.manyFluxCalc(self._phiArray, self._wavelenStep)
409 return outputList
412 def fluxListForSed(self, sedobj, indices=None):
413 """
414 Return a list of Fluxes for a single Sed object.
416 @param [in] sedobj is an Sed object. Its wavelength grid can be arbitrary. If necessary,
417 a copy will be created and resampled onto the wavelength grid of the Bandpasses before
418 fluxes are calculated. The original Sed will be unchanged.
420 @param [in] indices is an optional list of indices indicating which bandpasses to actually
421 calculate fluxes for. Other fluxes will be listed as numpy.NaN (i.e. this method will
422 return as many fluxes as were loaded with the loadBandpassesFromFiles methods; it will
423 just return numpy.NaN for fluxes you did not actually ask for)
425 @param [out] fluxList is a list of fluxes in the bandpasses stored in this BandpassDict
427 Note on units: Fluxes calculated this way will be the flux density integrated over the
428 weighted response curve of the bandpass. See equaiton 2.1 of the LSST Science Book
430 http://www.lsst.org/scientists/scibook
431 """
433 if sedobj.wavelen is not None:
435 # If the Sed's wavelength grid agrees with self._wavelen_match to one part in
436 # 10^6, just use the Sed as-is. Otherwise, copy it and resample it onto
437 # self._wavelen_match
438 if sedobj._needResample(wavelen_match=self._wavelen_match):
439 dummySed = Sed(wavelen=sedobj.wavelen, flambda=sedobj.flambda)
440 dummySed.resampleSED(force=True, wavelen_match=self._wavelen_match)
441 else:
442 dummySed = sedobj
444 return numpy.array(self._fluxListForSed(dummySed, indices=indices))
446 else:
447 return numpy.array([numpy.NaN]*len(self._bandpassDict))
450 def fluxDictForSed(self, sedobj, indices=None):
451 """
452 Return an OrderedDict of fluxes for a single Sed object.
454 The OrderedDict will be keyed off of the keys for this BandpassDict
456 @param [in] sedobj is an Sed object. Its wavelength grid can be arbitrary. If necessary,
457 a copy will be created and resampled onto the wavelength grid of the Bandpasses before
458 fluxes are calculated. The original Sed will be unchanged.
460 @param [in] indices is an optional list of indices indicating which bandpasses to actually
461 calculate fluxes for. Other fluxes will be listed as numpy.NaN (i.e. this method will
462 return as many fluxes as were loaded with the loadBandpassesFromFiles methods; it will
463 just return numpy.NaN for fluxes you did not actually ask for)
465 @param [out] fluxList is a list of fluxes in the bandpasses stored in this BandpassDict
467 Note on units: Fluxes calculated this way will be the flux density integrated over the
468 weighted response curve of the bandpass. See equaiton 2.1 of the LSST Science Book
470 http://www.lsst.org/scientists/scibook
471 """
472 fluxList = self.fluxListForSed(sedobj, indices=indices)
474 outputDict = OrderedDict()
476 for ix, bp in enumerate(self._bandpassDict.keys()):
477 outputDict[bp] = fluxList[ix]
479 return outputDict
482 def fluxListForSedList(self, sedList, indices=None):
483 """
484 Return a 2-D array of fluxes from a SedList.
485 Each row will correspond to a different Sed, each column
486 will correspond to a different bandpass, i.e. in the case of
488 flux = myBandpassDict.fluxListForSedList(mySedList)
490 flux[0][0] will be the flux of the 0th Sed in the 0th bandpass
491 flux[0][1] will be the flux of the 0th Sed in the 1st bandpass
492 flux[1][1] will be the flux of the 1st Sed in the 1st bandpass
493 etc.
495 For maximum efficiency, use the wavelenMatch keyword when loading
496 SEDs into your SedList and make sure that wavelenMatch = myBandpassDict.wavelenMatch.
497 That way, this method will not have to waste time resampling the Seds
498 onto the wavelength grid of the BandpassDict.
500 @param [in] sedList is a SedList containing the Seds
501 whose fluxes are desired.
503 @param [in] indices is an optional list of indices indicating which bandpasses to actually
504 calculate fluxes for. Other fluxes will be listed as numpy.NaN (i.e. this method will
505 return as many fluxes as were loaded with the loadBandpassesFromFiles methods; it will
506 just return numpy.NaN for fluxes you did not actually ask for)
508 @param [out] output_list is a 2-D numpy array containing the fluxes
509 of each Sed (the rows) in each bandpass contained in this BandpassDict
510 (the columns)
512 Note on units: Fluxes calculated this way will be the flux density integrated over the
513 weighted response curve of the bandpass. See equaiton 2.1 of the LSST Science Book
515 http://www.lsst.org/scientists/scibook
516 """
518 one_at_a_time = False
519 if sedList.wavelenMatch is None:
520 one_at_a_time = True
521 elif sedList[0]._needResample(wavelen_match=self._wavelen_match):
522 one_at_a_time = True
524 output_list = []
525 if one_at_a_time:
526 for sed_obj in sedList:
527 sub_list = self.fluxListForSed(sed_obj, indices=indices)
528 output_list.append(sub_list)
529 else:
530 # the difference between this block and the block above is that the block
531 # above performs the additional check of making sure that sed_obj.wavelen
532 # is equivalent to self._wavelen_match
533 for sed_obj in sedList:
534 sub_list = self._fluxListForSed(sed_obj, indices=indices)
535 output_list.append(sub_list)
537 return numpy.array(output_list)
540 def fluxArrayForSedList(self, sedList, indices=None):
541 """
542 Return a dtyped numpy array of fluxes from a SedList.
543 The array will be keyed to the keys of this BandpassDict,
544 i.e. in the case of
546 flux = myBandpassDict.fluxArrayForSedList(mySedList)
548 flux['u'][0] will be the flux of the 0th Sed in the 'u' bandpass
549 flux['u'][1] will be the flux of the 1st Sed in the 'u' bandpass
550 flux['z'] will be a numpy array of every Sed's flux in the 'z' bandpass
551 etc.
553 For maximum efficiency, use the wavelenMatch keyword when loading
554 SEDs into your SedList and make sure that wavelenMatch = myBandpassDict.wavelenMatch.
555 That way, this method will not have to waste time resampling the Seds
556 onto the wavelength grid of the BandpassDict.
558 @param [in] sedList is a SedList containing the Seds
559 whose fluxes are desired.
561 @param [in] indices is an optional list of indices indicating which bandpasses to actually
562 calculate fluxes for. Other fluxes will be listed as numpy.NaN (i.e. this method will
563 return as many fluxes as were loaded with the loadBandpassesFromFiles methods; it will
564 just return numpy.NaN for fluxes you did not actually ask for)
566 @param [out] output_list is a 2-D numpy array containing the fluxes
567 of each Sed (the rows) in each bandpass contained in this BandpassDict
568 (the columns)
570 Note on units: Fluxes calculated this way will be the flux density integrated over the
571 weighted response curve of the bandpass. See equaiton 2.1 of the LSST Science Book
573 http://www.lsst.org/scientists/scibook
574 """
576 fluxList = self.fluxListForSedList(sedList, indices=indices)
578 dtype = numpy.dtype([(bp, numpy.float) for bp in self._bandpassDict.keys()])
580 outputArray = numpy.array([tuple(row) for row in fluxList], dtype=dtype)
582 return outputArray
585 @property
586 def phiArray(self):
587 """
588 A 2-D numpy array storing the values of phi (see eqn 2.3 of the science
589 book) for all of the bandpasses in this dict.
590 """
591 return self._phiArray
594 @property
595 def wavelenStep(self):
596 """
597 The step size of the wavelength grid for all of the bandpasses
598 stored in this dict.
599 """
600 return self._wavelenStep
603 @property
604 def wavelenMatch(self):
605 """
606 The wavelength grid (in nm) on which all of the bandpass
607 throughputs have been sampled.
608 """
609 return self._wavelen_match