Skip to content

Commit

Permalink
Merge pull request #40 from hover2pi/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
hover2pi authored Jun 22, 2022
2 parents 5481109 + 65e7e9b commit b0a8478
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 12 deletions.
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

setup(
name='svo_filters',
version='0.4.2',
version='0.4.3',
description='A Python wrapper for the SVO Filter Profile Service',
packages=find_packages(
".",
Expand Down
40 changes: 29 additions & 11 deletions svo_filters/svo.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from bokeh.plotting import figure, show
import bokeh.palettes as bpal
import numpy as np
from .utils import incremented_monotonic


warnings.simplefilter('ignore', category=AstropyWarning)
Expand Down Expand Up @@ -94,7 +95,8 @@ class Filter:
The SVO filter ID
"""
def __init__(self, band, filter_directory=None, wave_units=q.um, flux_units=q.erg/q.s/q.cm**2/q.AA, **kwargs):
def __init__(self, band, filter_directory=None, wave_units=q.um, flux_units=q.erg/q.s/q.cm**2/q.AA,
monotonic=True, **kwargs):
"""
Loads the bandpass data into the Filter object
Expand All @@ -108,10 +110,16 @@ def __init__(self, band, filter_directory=None, wave_units=q.um, flux_units=q.er
The wavelength units
flux_units: str, astropy.units.core.PrefixUnit (optional)
The zeropoint flux units
monotonic: bool
Default = True. Whether to add a small offset to repeated elements in wavelength array
to ensure montonically increasing wavelengths.
"""
if filter_directory is None:
filter_directory = resource_filename('svo_filters', 'data/filters/')

# Whether to ensure wavelengths returned should be strictly monotonically increasing.
self.monotonic = monotonic

# Check if TopHat
if band.lower().replace('-', '').replace(' ', '') == 'tophat':

Expand All @@ -126,6 +134,7 @@ def __init__(self, band, filter_directory=None, wave_units=q.um, flux_units=q.er
else:
# Load the filter
n_pix = kwargs.get('n_pixels', 100)
self.monotonic = False # Never ensure monotonic for tophat as that can mess up a tophat profile?
self.load_TopHat(wave_min, wave_max, n_pix)

else:
Expand Down Expand Up @@ -160,19 +169,21 @@ def __init__(self, band, filter_directory=None, wave_units=q.um, flux_units=q.er
# Otherwise try a Web query or throw an error
else:

err = """No filters match {}\n\nFILTERS ON FILE: {}\n\nA full list of available filters from the\nSVO Filter Profile Service can be found at\nhttp: //svo2.cab.inta-csic.es/theory/fps3/\n\nTry again with the desired filter as '<facility>/<instrument>.<filter_name>', e.g. '2MASS/2MASS.J'""".format(band, ', '.join(bands))
err = f"No filters match {band}\n\nFILTERS ON FILE: \n\nFILTERS ON FILE: {', '.join(bands)}\n\n"
"A full list of available filters from the\nSVO Filter Profile Service can be found at\n"
"http: //svo2.cab.inta-csic.es/theory/fps3/\n\nTry again with the desired filter as "
"'<facility>/<instrument>.<filter_name>', e.g. '2MASS/2MASS.J'"

# Valid SVO filter names have backslash
if '/' not in band:
raise IndexError(err)

# Try a web query
if '/' in band:
try:
self.load_web(band)
except:
raise IndexError(err)

else:

# Or throw an error
try:
self.load_web(band)
except IndexError:
raise IndexError(err)
# Make sure we return e.g. Connection Error, but are not catching e.g. SysExit calls.

# Set the wavelength and throughput
self._wave_units = q.AA
Expand Down Expand Up @@ -725,6 +736,9 @@ def plot(self, details=False, fig=None, draw=True):
else:
return fig




@property
def rsr(self):
"""A getter for the relative spectral response (rsr) curve"""
Expand Down Expand Up @@ -755,6 +769,10 @@ def throughput(self, points):
@property
def wave(self):
"""A getter for the wavelength"""
# Check wavelength is monotonically increasing

if self.monotonic:
self._wave = incremented_monotonic(self._wave, increment_step=1000)
return self._wave

@wave.setter
Expand Down
6 changes: 6 additions & 0 deletions svo_filters/test_svo.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,12 @@ def test_plot(self):

self.assertTrue(type(plt) == Figure)

def test_filter_monotonic(self):
"""Test that non-monotonic filters are treated correctly"""
filt = svo.Filter('Palomar/ZTF.g')
self.assertFalse(np.any(np.diff(filt.wave)<=0))


class TestFilterList(unittest.TestCase):
"""Tests for filter function"""
def setUp(self):
Expand Down
123 changes: 123 additions & 0 deletions svo_filters/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import astropy.units as q
import numpy as np


def _padded_differences(arr, pad_val=1e20):
"""
Parameters
----------
arr: array-like
pad_val: astropy.units.quantity, float, int
value for padding the first element of the difference array by when calculating
monotonically increasing (or not) differences.
"""
if isinstance(arr, q.Quantity):
# Quantity units cannot be padded by non-quantity unless using this wrapper.
diff = arr.ediff1d(to_begin=pad_val)
else:
diff = np.ediff1d(arr, to_begin=pad_val)
return diff

def incremented_monotonic(arr, increment=None, increment_step=1000):
"""Returns input if monotonically increasing. Otherwise
increment repeated elements by `increment`, which will be set to 1/`increment_step`
of the smallest difference in array if `None`, the default.
If not monotonically increasing (ignoring repeated elements), raises `ValueError`.
Parameters
----------
arr: array-like
array to check for increment. Also handles astropy.units.quantity.Quantity arrays.
increment: astropy.units.quantity, float, int (optional)
value to increment repeated elements by. Set to 1/`increment_step` of the smallest difference
in array if `None`, the default. Unit conversion will be attempted if array is an instance
of astropy.units.quantity.Quantity.
increment_step: float, int
The relative size difference between repeated elements if automatically determining. Only
used if `increment = None`.
Returns
-------
array-like
Input array if monotonically increasing else input array where repeat values
have been incremented by `increment`.
"""
diff = _padded_differences(arr)

if np.any(diff<0):
raise ValueError("Input array must be monotonically increasing except for repeated values.")
non_monotonic_mask = diff<=0

# Exit early if monotonic
if np.all(~non_monotonic_mask):
return arr

if increment is None:
# a thousanth of the minimimum non-zero increment.
increment = np.nanmin(diff[~non_monotonic_mask])/increment_step
# Try to help user with unit conversion, will fail if unconvertable.
elif isinstance(arr, q.Quanity):
increment = increment << arr.unit

#non_monotonic_mask = non_monotonic_mask.reshape(arr.shape)
repeats, multiples = get_multipliers(non_monotonic_mask)
if isinstance(increment, q.Quantity):
multiples = multiples << q.dimensionless_unscaled
multiples *= increment

flatarr = arr.flatten()
flatarr[repeats] += multiples
return flatarr.reshape(arr.shape)


def breadth_first(repeats, state, row):
"""somewhat convoluted 1D breadth first search
for getting multiples of repeated elements.
"""
queue = [repeats[row]]
index = 1
additions = [index]
state.append(row)
while len(queue) > 0:
idx = queue.pop(0)

#print(idx, idx+1, repeats)
if idx+1 in repeats:
neighbor = idx+1
state.append(row+index)
index += 1
queue.append(neighbor)
additions.append(index)

return additions, state


def get_multipliers(mask):
"""Get all repeats and their multiples using breadth first search
Parameters
----------
mask: array_like
array of booleans representing repeated elements. True for repeated.
Returns
-------
tuple
tuple of array_like of repeated indexes and their multiples for use in
shifting multiple repeated indexes.
"""
repeats = np.nonzero(mask.flatten())[0]
if len(repeats)==0:
raise ValueError("No repeats found. Input mask all False")
groups = []
state = []
for j,val in enumerate(repeats):
if j in state:
continue
additions, state = breadth_first(repeats, state, j)
groups.append(additions)
groups = np.array([element for sublist in groups for element in sublist])

return repeats, groups

0 comments on commit b0a8478

Please sign in to comment.