diff --git a/docs/index.rst b/docs/index.rst index 90f450d..8ca4439 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -21,6 +21,53 @@ Contents Simple example ^^^^^^^^^^^^^^ +Find available filters like this: + +.. code-block:: python + + >>> from tynt.lookup import filters + >>> # filters. # this works in a Jupyter environment, OR: + >>> dir(filters) # this works in any interactive environment + ['CAHA', + 'CHEOPS', + 'CTIO', + 'GAIA', + ... + +To see the filters available within a filter set: + +.. code-block:: python + + >>> from tynt.lookup import filters + + >>> filters.Kepler + + +Plot the filter transmittance curve: + +.. plot:: + :include-source: + + from tynt.lookup import filters + + filters.Kepler.Kepler_K.plot() + +Compare several optical filters: + +.. plot:: + :include-source: + + from tynt.lookup import filters + + filters.Kepler.Kepler_K.plot(label='Kepler') + filters.CHEOPS.CHEOPS_band.plot(label='CHEOPS') + filters.TESS.TESS_Red.plot(label='TESS') + + +FilterGenerator +^^^^^^^^^^^^^^^ + Let's plot the transmittance curve of the SDSS r' filter: .. plot:: diff --git a/setup.cfg b/setup.cfg index d575244..b3031ee 100644 --- a/setup.cfg +++ b/setup.cfg @@ -17,6 +17,8 @@ setup_requires = setuptools_scm install_requires = numpy astropy + matplotlib + scipy [options.extras_require] all = diff --git a/tynt/core.py b/tynt/core.py index a6e4c0f..0fe0649 100644 --- a/tynt/core.py +++ b/tynt/core.py @@ -1,12 +1,15 @@ import os import numpy as np +import matplotlib.pyplot as plt + import astropy.units as u from astropy.io import fits -from astropy.table import Table -from astropy.utils.data import download_file from astropy.modeling import models from astropy.modeling.tabular import Tabular1D +from astropy.table import Table +from astropy.utils.data import download_file +from astropy.visualization import quantity_support __all__ = ['FilterGenerator', 'Filter'] @@ -17,7 +20,7 @@ class FilterGenerator: """ Astronomical filter object generator. """ - def __init__(self, path=None): + def __init__(self, path=None, name=None): """ Parameters ---------- @@ -28,6 +31,7 @@ def __init__(self, path=None): path = data_path self.path = path + self.name = name self.table = Table(fits.getdata(path)) self.table.add_index('Filter name') @@ -152,8 +156,11 @@ def fft_model(x): astropy_model = fft_model() + filter_set, filter_name = identifier.split('/') return Filter( - wavelength * u.Angstrom, transmittance, model=astropy_model + wavelength * u.Angstrom, transmittance, + filter_set=filter_set, filter_name=identifier, + model=astropy_model ) def download_true_transmittance(self, identifier, **kwargs): @@ -174,18 +181,28 @@ def download_true_transmittance(self, identifier, **kwargs): f'theory/fps3/fps.php?ID={identifier}', **kwargs) true_transmittance = Table.read(path, format='votable') + + filter_set, filter_name = identifier.split('/') + return Filter( true_transmittance['Wavelength'].data.data * u.Angstrom, - true_transmittance['Transmission'].data.data + true_transmittance['Transmission'].data.data, + filter_set=filter_set, filter_name=identifier ) +_filter_generator = FilterGenerator() + + class Filter: """ Astronomical filter. """ @u.quantity_input(wavelength=u.m) - def __init__(self, wavelength, transmittance, model=None): + def __init__( + self, wavelength=None, transmittance=None, + model=None, filter_set=None, filter_name=None + ): """ Parameters ---------- @@ -196,9 +213,39 @@ def __init__(self, wavelength, transmittance, model=None): model : ~astropy.modeling.Model Astropy model for the transmittance curve """ - self.wavelength = wavelength - self.transmittance = transmittance + self._wavelength = wavelength + self._transmittance = transmittance self.model = model + self.filter_set = filter_set + self.filter_name = filter_name + + @property + def wavelength(self): + if self._wavelength is None: + self._get_filter_from_name() + return self._wavelength + + @wavelength.setter + def wavelength(self, value): + if value is not None: + self._wavelength = value + + @property + def transmittance(self): + if self._transmittance is None: + self._get_filter_from_name() + return self._transmittance + + @transmittance.setter + def transmittance(self, value): + if value is not None: + self._transmittance = value + + def _get_filter_from_name(self): + name = f"{self.filter_set}/{self.filter_name.replace('__', '.')}" + filt = _filter_generator.reconstruct(name) + self.wavelength = filt.wavelength + self.transmittance = filt.transmittance @property def table(self): @@ -212,3 +259,25 @@ def table(self): """ return Tabular1D(points=self.wavelength, lookup_table=self.transmittance) + + def __repr__(self): + if None not in (self.filter_name, self.filter_set): + return f"" + return "" + + def plot(self, ax=None, x_unit=u.um, y_label='Transmittance', **kwargs): + + if ax is None: + ax = plt.gca() + + label = kwargs.pop('label', self.filter_name) + + with quantity_support(): + ax.plot( + self.wavelength.to(x_unit), self.transmittance, + label=label, **kwargs + ) + ax.legend() + + if y_label is not None: + ax.set(ylabel=y_label) diff --git a/tynt/lookup.py b/tynt/lookup.py new file mode 100644 index 0000000..e374656 --- /dev/null +++ b/tynt/lookup.py @@ -0,0 +1,73 @@ +from collections import defaultdict +from tynt.core import Filter, _filter_generator + +__all__ = ['filters'] + + +class FilterSet: + + def __init__(self, filter_set, filter_names): + self._filter_set = filter_set + self._filter_names = filter_names + self.filters = set() + for filter_name in filter_names: + fname = filter_name.replace('.', '_') + setattr( + self, fname, + Filter(filter_set=filter_set, filter_name=filter_name) + ) + self.filters.add(f'"{fname}"') + + def __repr__(self): + return (f"") + + +def assemble_filter_sets(): + + filter_sets = defaultdict(list) + + for name in _filter_generator.available_filters(): + filter_set, filter_name = name.split('/') + filter_sets[filter_set].append(filter_name) + + sets = dict() + for filter_set, filter_names in filter_sets.items(): + sets[filter_set] = FilterSet(filter_set, filter_names) + + return sets + + +class DefaultFacilities: + TWOMASS = None + SLOAN = None + Kepler = None + TESS = None + HST = None + JWST = None + LSST = None + Keck = None + WISE = None + WFIRST = None + Roman = None + Spitzer = None + GAIA = None + CHEOPS = None + + +class FilterLookup(DefaultFacilities): + _accessors = set() + + def __init__(self): + + filter_sets = assemble_filter_sets() + + for name, filter_set in filter_sets.items(): + if name[0].isnumeric(): + name = name.replace('2', 'TWO') + + setattr(self, name, filter_set) + self._accessors.add(name) + + +filters = FilterLookup() diff --git a/tynt/tests/test_core.py b/tynt/tests/test_core.py index f3f8c7b..41def19 100644 --- a/tynt/tests/test_core.py +++ b/tynt/tests/test_core.py @@ -1,9 +1,11 @@ import pytest import numpy as np -from astropy.tests.helper import assert_quantity_allclose + import astropy.units as u +from astropy.tests.helper import assert_quantity_allclose +from scipy.integrate import trapezoid -from ..core import FilterGenerator +from tynt.core import FilterGenerator filter_generator = FilterGenerator() @@ -68,11 +70,11 @@ def test_lambda_eff_w_eff( for identifier in filters: filt = filter_generator.reconstruct(identifier) - lambda_bar_approx = (np.trapz(filt.transmittance * filt.wavelength, - filt.wavelength) / - np.trapz(filt.transmittance, filt.wavelength)) + lambda_bar_approx = (trapezoid(filt.transmittance * filt.wavelength, + filt.wavelength) / + trapezoid(filt.transmittance, filt.wavelength)) - width_approx = (np.trapz(filt.transmittance, filt.wavelength) / + width_approx = (trapezoid(filt.transmittance, filt.wavelength) / filt.transmittance.max()) w_eff_approx.append(width_approx)