Coverage for src/ufig/mask_utils.py: 94%

59 statements  

« prev     ^ index     » next       coverage.py v7.10.2, created at 2025-08-07 15:17 +0000

1# Copyright (C) 2019 ETH Zurich, Institute for Particle and Astrophysics 

2 

3""" 

4Created on Feb 5, 2019 

5author: Joerg Herbel 

6""" 

7 

8import numba as nb 

9import numpy as np 

10 

11 

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 

17 

18 arr_decimal[i] = np.sum(2**np.arange(n_bits) * arr_binary[i]) 

19 

20 This means that this binary representation is reversed with respect to what is 

21 normally used. For example, normally, 

22 

23 1100 = 1 * 2**3 + 1 * 2**2 + 0 * 2**1 + 0 * 2**0. 

24 

25 However, here we have 

26 

27 1100 = 1 * 2**0 + 1 * 2**1 + 0 * 2**2 + 0 * 2**3. 

28 

29 ATTENTION: THIS FUNCTION MODIFIES ARR_DECIMAL IN PLACE! 

30 """ 

31 arr_binary = np.zeros((len(arr_decimal), n_bits), dtype=dtype_out) 

32 arr_decimal = arr_decimal.astype(dtype_out) 

33 for i in range(n_bits): 

34 arr_binary[:, i] = arr_decimal % np.uint(2) 

35 arr_decimal //= np.uint(2) 

36 

37 return arr_binary 

38 

39 

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 

44 

45 raise ValueError( 

46 f"Number of bits {n_bits} exceeding maximum possible value such that np.uint" 

47 " dtype can be used" 

48 ) 

49 

50 

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 

56 :param pixel_mask: mask 

57 :param maskbits_keep: mask bits to keep 

58 :param n_bits: ... 

59 :return: mask with only bits to keep switched on (same shape as input mask) 

60 """ 

61 

62 pixel_mask = pixel_mask.copy() 

63 

64 # special case of no masking 

65 if len(maskbits_keep) == 0: 65 ↛ 66line 65 didn't jump to line 66 because the condition on line 65 was never true

66 return pixel_mask * 0 

67 

68 # transform to binary representation 

69 pixel_mask_bin = decimal_integer_to_binary( 

70 n_bits, np.ravel(pixel_mask), dtype_out=get_binary_mask_dtype(n_bits) 

71 ) 

72 

73 # select bits to keep 

74 pixel_mask_bin = pixel_mask_bin[:, maskbits_keep] 

75 

76 # transform back to original shape 

77 pixel_mask_keep = np.sum(pixel_mask_bin, axis=1).reshape(pixel_mask.shape) 

78 

79 return pixel_mask_keep 

80 

81 

82@nb.jit(nopython=True) 

83def select_off_mask(xs, ys, rs, mask): 

84 # Array holding results 

85 select_off = np.ones(len(xs), dtype=np.int8) 

86 

87 # Compute coordinates to be checked 

88 x_min = np.floor(xs - rs).astype(np.int32) 

89 y_min = np.floor(ys - rs).astype(np.int32) 

90 x_max = np.ceil(xs + rs).astype(np.int32) 

91 y_max = np.ceil(ys + rs).astype(np.int32) 

92 

93 x_min = np.maximum(x_min, 0) 

94 y_min = np.maximum(y_min, 0) 

95 x_max = np.minimum(x_max, mask.shape[1]) 

96 y_max = np.minimum(y_max, mask.shape[0]) 

97 

98 rs_sq = rs**2 

99 

100 for i in range(len(xs)): 

101 x = xs[i] 

102 y = ys[i] 

103 r_sq = rs_sq[i] 

104 

105 for xi in range(x_min[i], x_max[i]): 

106 for yi in range(y_min[i], y_max[i]): 

107 delta = (xi - x) ** 2 + (yi - y) ** 2 

108 if delta < r_sq and mask[int(yi), int(xi)] > 0: 

109 select_off[i] = 0 

110 break 

111 if select_off[i] == 0: 

112 break 

113 

114 return select_off 

115 

116 

117def pixel_mask_to_catalog_mask(pixel_mask, cat, off_mask_radius): 

118 x = cat["XWIN_IMAGE"] - 0.5 

119 y = cat["YWIN_IMAGE"] - 0.5 

120 r = off_mask_radius * cat["FLUX_RADIUS"] 

121 r[r < 0] = 5 

122 

123 return select_off_mask(x, y, r, pixel_mask).astype(bool) 

124 

125 

126def pixel_mask_to_ucat_catalog_mask(pixel_mask, cat, off_mask_radius, r50_offset=5): 

127 x = cat["x"] 

128 y = cat["y"] 

129 r = off_mask_radius * cat["r50"] + r50_offset 

130 r[r < 0] = 5 

131 

132 return select_off_mask(x, y, r, pixel_mask).astype(bool)