import logging
import warnings
import numpy as np
log = logging.getLogger(__name__)
__all__ = ["get_box_weights", "box_extract", "estim_error_nearest_data"]
[docs]
def get_box_weights(centroid, n_pix, shape, cols):
"""
Return the weights of a box aperture given the centroid and the width of the box in pixels.
All pixels will have the same weights except at the ends of the box aperture.
Parameters
----------
centroid : ndarray
Position of the centroid (in rows). Same shape as ``cols``.
n_pix : float
Width of the extraction box in pixels.
shape : tuple
Shape of the output image, i.e., ``(n_row, n_column)``.
cols : array
Column indices (int) of good columns. Used if the centroid is defined
for specific columns or a sub-range of columns.
Returns
-------
weights : ndarray
An array of pixel weights to use with the box extraction.
"""
nrows, _ = shape
# Row centers of all pixels.
rows = np.indices((nrows, len(cols)))[0]
# Pixels that are entirely inside the box are set to one.
cond = rows <= (centroid - 0.5 + n_pix / 2)
cond &= (centroid + 0.5 - n_pix / 2) <= rows
weights = cond.astype(float)
# Fractional weights at the upper bound.
cond = (centroid - 0.5 + n_pix / 2) < rows
cond &= rows < (centroid + 0.5 + n_pix / 2)
weights[cond] = (centroid + n_pix / 2 - (rows - 0.5))[cond]
# Fractional weights at the lower bound.
cond = rows < (centroid + 0.5 - n_pix / 2)
cond &= (centroid - 0.5 - n_pix / 2) < rows
weights[cond] = (rows + 0.5 - (centroid - n_pix / 2))[cond]
# Return with the specified shape with zeros where the box is not defined.
out = np.zeros(shape, dtype=float)
out[:, cols] = weights
return out
[docs]
def estim_error_nearest_data(err, data, pix_to_estim, valid_pix):
"""
Estimate pixel error empirically using error on nearby pixel values in data.
Intended to be used in a box extraction when the bad pixels are modeled.
Parameters
----------
err : ndarray
Uncertainty map (2D) of the pixels.
data : ndarray
Pixel values (2D).
pix_to_estim : ndarray
Boolean map (2D) of the pixels where the uncertainty needs to be estimated.
valid_pix : ndarray
Boolean map (2D) of valid pixels to be used to find the error empirically.
Returns
-------
err_filled : ndarray
Same as ``err`` (2D), but the pixels to be estimated are filled with the estimated values.
Notes
-----
For uncorrelated noise, the average error on the replaced pixels will be roughly
half of the average error on the original good pixels.
The reason is because this method chooses the lower error between the two nearest-flux
data points, leading to a factor-of-2 decrease (assuming errors are normally distributed).
Future work should follow up on whether this remains the desired behavior.
"""
# Transform to 1d arrays
data_to_estim = data[pix_to_estim]
err_valid = err[valid_pix]
data_valid = data[valid_pix]
# Need to sort the arrays used to find similar values
idx_sort = np.argsort(data_valid)
err_valid = err_valid[idx_sort]
data_valid = data_valid[idx_sort]
# Searchsorted: gives the position of the nearest higher value,
# not necessarily the closest value
idx_higher = np.searchsorted(data_valid, data_to_estim)
idx_higher = np.clip(idx_higher, 0, err_valid.size - 1)
# The nearest lower value is given by the preceding index
idx_lower = np.clip(idx_higher - 1, 0, err_valid.size - 1)
# Find the best between index around the value (lower and higher index) ...
idx_around = np.vstack([idx_lower, idx_higher])
# ... using the one with the smallest error
distance = np.abs(data_valid[idx_around] - data_to_estim[None, :])
idx_best_of_2 = np.argmin(distance, axis=0)
idx_closest = idx_around[idx_best_of_2, np.arange(idx_best_of_2.size)]
# Get the corresponding error (that's what we want to find!)
err_estimate = err_valid[idx_closest]
# Replace estimated values in the output error 2D image
err_out = err.copy()
err_out[pix_to_estim] = err_estimate
return err_out