Coverage for src/ufig/mask_utils.py: 93%
58 statements
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-12 19:08 +0000
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-12 19:08 +0000
1# Copyright (C) 2019 ETH Zurich, Institute for Particle and Astrophysics
3"""
4Created on Feb 5, 2019
5author: Joerg Herbel
6"""
8import numba as nb
9import numpy as np
12def decimal_integer_to_binary(n_bits, arr_decimal, dtype_out):
13 """
14 Transform a one-dimensional array of decimal integers into its binary
15 representation. Returns an array with the shape (len(arr_decimal), n_bits). To
16 reconstruct arr_decimal[i], one would perform the operation
18 arr_decimal[i] = np.sum(2**np.arange(n_bits) * arr_binary[i])
20 This means that this binary representation is reversed with respect to what is
21 normally used. For example, normally,
23 1100 = 1 * 2**3 + 1 * 2**2 + 0 * 2**1 + 0 * 2**0.
25 However, here we have
27 1100 = 1 * 2**0 + 1 * 2**1 + 0 * 2**2 + 0 * 2**3.
29 ATTENTION: THIS FUNCTION MODIFIES ARR_DECIMAL IN PLACE!
30 """
31 arr_binary = np.zeros((len(arr_decimal), n_bits), dtype=dtype_out)
33 for i in range(n_bits):
34 arr_binary[:, i] = arr_decimal % np.uint(2)
35 arr_decimal //= np.uint(2)
37 return arr_binary
40def get_binary_mask_dtype(n_bits):
41 for dtype in [np.uint8, np.uint16, np.uint32, np.uint64]: 41 ↛ 45line 41 didn't jump to line 45 because the loop on line 41 didn't complete
42 if np.iinfo(dtype).max >= n_bits: 42 ↛ 41line 42 didn't jump to line 41 because the condition on line 42 was always true
43 return dtype
45 raise ValueError(
46 f"Number of bits {n_bits} exceeding maximum possible value such that np.uint"
47 " dtype can be used"
48 )
51def set_masked_pixels(pixel_mask, maskbits_keep, n_bits):
52 """
53 Set pixels which are masked according to input bits. This function modifies
54 pixel_mask in-place.
55 :param pixel_mask: mask
56 :param maskbits_keep: mask bits to keep
57 :param n_bits: ...
58 :return: mask with only bits to keep switched on (same shape as input mask)
59 """
61 pixel_mask = pixel_mask.copy()
63 # special case of no masking
64 if len(maskbits_keep) == 0: 64 ↛ 65line 64 didn't jump to line 65 because the condition on line 64 was never true
65 return pixel_mask * 0
67 # transform to binary representation
68 pixel_mask_bin = decimal_integer_to_binary(
69 n_bits, np.ravel(pixel_mask), dtype_out=get_binary_mask_dtype(n_bits)
70 )
72 # select bits to keep
73 pixel_mask_bin = pixel_mask_bin[:, maskbits_keep]
75 # transform back to original shape
76 pixel_mask_keep = np.sum(pixel_mask_bin, axis=1).reshape(pixel_mask.shape)
78 return pixel_mask_keep
81@nb.jit(nopython=True)
82def select_off_mask(xs, ys, rs, mask):
83 # Array holding results
84 select_off = np.ones(len(xs), dtype=np.int8)
86 # Compute coordinates to be checked
87 x_min = np.floor(xs - rs).astype(np.int32)
88 y_min = np.floor(ys - rs).astype(np.int32)
89 x_max = np.ceil(xs + rs).astype(np.int32)
90 y_max = np.ceil(ys + rs).astype(np.int32)
92 x_min = np.maximum(x_min, 0)
93 y_min = np.maximum(y_min, 0)
94 x_max = np.minimum(x_max, mask.shape[1])
95 y_max = np.minimum(y_max, mask.shape[0])
97 rs_sq = rs**2
99 for i in range(len(xs)):
100 x = xs[i]
101 y = ys[i]
102 r_sq = rs_sq[i]
104 for xi in range(x_min[i], x_max[i]):
105 for yi in range(y_min[i], y_max[i]):
106 delta = (xi - x) ** 2 + (yi - y) ** 2
107 if delta < r_sq and mask[int(yi), int(xi)] > 0:
108 select_off[i] = 0
109 break
110 if select_off[i] == 0:
111 break
113 return select_off
116def pixel_mask_to_catalog_mask(pixel_mask, cat, off_mask_radius):
117 x = cat["XWIN_IMAGE"] - 0.5
118 y = cat["YWIN_IMAGE"] - 0.5
119 r = off_mask_radius * cat["FLUX_RADIUS"]
120 r[r < 0] = 5
122 return select_off_mask(x, y, r, pixel_mask).astype(bool)
125def pixel_mask_to_ucat_catalog_mask(pixel_mask, cat, off_mask_radius, r50_offset=5):
126 x = cat["x"]
127 y = cat["y"]
128 r = off_mask_radius * cat["r50"] + r50_offset
129 r[r < 0] = 5
131 return select_off_mask(x, y, r, pixel_mask).astype(bool)