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

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 

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 :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 """ 

60 

61 pixel_mask = pixel_mask.copy() 

62 

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 

66 

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 ) 

71 

72 # select bits to keep 

73 pixel_mask_bin = pixel_mask_bin[:, maskbits_keep] 

74 

75 # transform back to original shape 

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

77 

78 return pixel_mask_keep 

79 

80 

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) 

85 

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) 

91 

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]) 

96 

97 rs_sq = rs**2 

98 

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

100 x = xs[i] 

101 y = ys[i] 

102 r_sq = rs_sq[i] 

103 

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 

112 

113 return select_off 

114 

115 

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 

121 

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

123 

124 

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 

130 

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