Source code for ufig.mask_utils
# Copyright (C) 2019 ETH Zurich, Institute for Particle and Astrophysics
"""
Created on Feb 5, 2019
author: Joerg Herbel
"""
import numba as nb
import numpy as np
[docs]
def decimal_integer_to_binary(n_bits, arr_decimal, dtype_out):
"""
Transform a one-dimensional array of decimal integers into its binary
representation. Returns an array with the shape (len(arr_decimal), n_bits). To
reconstruct arr_decimal[i], one would perform the operation
arr_decimal[i] = np.sum(2**np.arange(n_bits) * arr_binary[i])
This means that this binary representation is reversed with respect to what is
normally used. For example, normally,
1100 = 1 * 2**3 + 1 * 2**2 + 0 * 2**1 + 0 * 2**0.
However, here we have
1100 = 1 * 2**0 + 1 * 2**1 + 0 * 2**2 + 0 * 2**3.
ATTENTION: THIS FUNCTION MODIFIES ARR_DECIMAL IN PLACE!
"""
arr_binary = np.zeros((len(arr_decimal), n_bits), dtype=dtype_out)
arr_decimal = arr_decimal.astype(dtype_out)
for i in range(n_bits):
arr_binary[:, i] = arr_decimal % np.uint(2)
arr_decimal //= np.uint(2)
return arr_binary
[docs]
def get_binary_mask_dtype(n_bits):
for dtype in [np.uint8, np.uint16, np.uint32, np.uint64]:
if np.iinfo(dtype).max >= n_bits:
return dtype
raise ValueError(
f"Number of bits {n_bits} exceeding maximum possible value such that np.uint"
" dtype can be used"
)
[docs]
def set_masked_pixels(pixel_mask, maskbits_keep, n_bits):
"""
Set pixels which are masked according to input bits. This function modifies
pixel_mask in-place.
:param pixel_mask: mask
:param maskbits_keep: mask bits to keep
:param n_bits: ...
:return: mask with only bits to keep switched on (same shape as input mask)
"""
pixel_mask = pixel_mask.copy()
# special case of no masking
if len(maskbits_keep) == 0:
return pixel_mask * 0
# transform to binary representation
pixel_mask_bin = decimal_integer_to_binary(
n_bits, np.ravel(pixel_mask), dtype_out=get_binary_mask_dtype(n_bits)
)
# select bits to keep
pixel_mask_bin = pixel_mask_bin[:, maskbits_keep]
# transform back to original shape
pixel_mask_keep = np.sum(pixel_mask_bin, axis=1).reshape(pixel_mask.shape)
return pixel_mask_keep
[docs]
@nb.jit(nopython=True)
def select_off_mask(xs, ys, rs, mask):
# Array holding results
select_off = np.ones(len(xs), dtype=np.int8)
# Compute coordinates to be checked
x_min = np.floor(xs - rs).astype(np.int32)
y_min = np.floor(ys - rs).astype(np.int32)
x_max = np.ceil(xs + rs).astype(np.int32)
y_max = np.ceil(ys + rs).astype(np.int32)
x_min = np.maximum(x_min, 0)
y_min = np.maximum(y_min, 0)
x_max = np.minimum(x_max, mask.shape[1])
y_max = np.minimum(y_max, mask.shape[0])
rs_sq = rs**2
for i in range(len(xs)):
x = xs[i]
y = ys[i]
r_sq = rs_sq[i]
for xi in range(x_min[i], x_max[i]):
for yi in range(y_min[i], y_max[i]):
delta = (xi - x) ** 2 + (yi - y) ** 2
if delta < r_sq and mask[int(yi), int(xi)] > 0:
select_off[i] = 0
break
if select_off[i] == 0:
break
return select_off
[docs]
def pixel_mask_to_catalog_mask(pixel_mask, cat, off_mask_radius):
x = cat["XWIN_IMAGE"] - 0.5
y = cat["YWIN_IMAGE"] - 0.5
r = off_mask_radius * cat["FLUX_RADIUS"]
r[r < 0] = 5
return select_off_mask(x, y, r, pixel_mask).astype(bool)
[docs]
def pixel_mask_to_ucat_catalog_mask(pixel_mask, cat, off_mask_radius, r50_offset=5):
x = cat["x"]
y = cat["y"]
r = off_mask_radius * cat["r50"] + r50_offset
r[r < 0] = 5
return select_off_mask(x, y, r, pixel_mask).astype(bool)