Coverage for src/bob/bio/vein/preprocessor/normalize.py: 92%

52 statements  

« prev     ^ index     » next       coverage.py v7.6.0, created at 2024-07-12 23:27 +0200

1#!/usr/bin/env python 

2# vim: set fileencoding=utf-8 : 

3 

4"""Base utilities for normalization""" 

5 

6import math 

7 

8import numpy 

9 

10from PIL import Image 

11 

12 

13class Normalizer(object): 

14 """Objects of this class normalize the input image orientation and scale""" 

15 

16 def __init__(self): 

17 pass 

18 

19 def __call__(self, image, mask): 

20 """Inputs image and mask and outputs a normalized version of those 

21 

22 

23 Parameters: 

24 

25 image (numpy.ndarray): raw image to normalize as 2D array of unsigned 

26 8-bit integers 

27 

28 mask (numpy.ndarray): mask to normalize as 2D array of booleans 

29 

30 

31 Returns: 

32 

33 numpy.ndarray: A 2D boolean array with the same shape and data type of 

34 the input image representing the newly aligned image. 

35 

36 numpy.ndarray: A 2D boolean array with the same shape and data type of 

37 the input mask representing the newly aligned mask. 

38 

39 """ 

40 

41 raise NotImplementedError("You must implement the __call__ slot") 

42 

43 

44class NoNormalization(Normalizer): 

45 """Trivial implementation with no normalization""" 

46 

47 def __init__(self): 

48 pass 

49 

50 def __call__(self, image, mask): 

51 """Returns the input parameters, without changing them 

52 

53 

54 Parameters: 

55 

56 image (numpy.ndarray): raw image to normalize as 2D array of unsigned 

57 8-bit integers 

58 

59 mask (numpy.ndarray): mask to normalize as 2D array of booleans 

60 

61 

62 Returns: 

63 

64 numpy.ndarray: A 2D boolean array with the same shape and data type of 

65 the input image representing the newly aligned image. 

66 

67 numpy.ndarray: A 2D boolean array with the same shape and data type of 

68 the input mask representing the newly aligned mask. 

69 

70 """ 

71 

72 return image, mask 

73 

74 

75class HuangNormalization(Normalizer): 

76 """Simple finger normalization from Huang et. al 

77 

78 Based on B. Huang, Y. Dai, R. Li, D. Tang and W. Li, Finger-vein 

79 authentication based on wide line detector and pattern normalization, 

80 Proceedings on 20th International Conference on Pattern Recognition (ICPR), 

81 2010. 

82 

83 This implementation aligns the finger to the centre of the image using an 

84 affine transformation. Elliptic projection which is described in the 

85 referenced paper is **not** included. 

86 

87 In order to defined the affine transformation to be performed, the 

88 algorithm first calculates the center for each edge (column wise) and 

89 calculates the best linear fit parameters for a straight line passing 

90 through those points. 

91 """ 

92 

93 def __init__(self, padding_width=5, padding_constant=51): 

94 self.padding_width = padding_width 

95 self.padding_constant = padding_constant 

96 

97 def __call__(self, image, mask): 

98 """Inputs image and mask and outputs a normalized version of those 

99 

100 

101 Parameters: 

102 

103 image (numpy.ndarray): raw image to normalize as 2D array of unsigned 

104 8-bit integers 

105 

106 mask (numpy.ndarray): mask to normalize as 2D array of booleans 

107 

108 

109 Returns: 

110 

111 numpy.ndarray: A 2D boolean array with the same shape and data type of 

112 the input image representing the newly aligned image. 

113 

114 numpy.ndarray: A 2D boolean array with the same shape and data type of 

115 the input mask representing the newly aligned mask. 

116 

117 """ 

118 

119 img_h, img_w = image.shape 

120 

121 # Calculates the mask edges along the columns 

122 edges = numpy.zeros((2, mask.shape[1]), dtype=int) 

123 

124 edges[0, :] = mask.argmax(axis=0) # get upper edges 

125 edges[1, :] = len(mask) - numpy.flipud(mask).argmax(axis=0) - 1 

126 

127 bl = edges.mean(axis=0) # baseline 

128 x = numpy.arange(0, edges.shape[1]) 

129 A = numpy.vstack([x, numpy.ones(len(x))]).T 

130 

131 # Fit a straight line through the base line points 

132 w = numpy.linalg.lstsq(A, bl)[0] # obtaining the parameters 

133 

134 angle = -1 * math.atan(w[0]) # Rotation 

135 tr = img_h / 2 - w[1] # Translation 

136 scale = 1.0 # Scale 

137 

138 # Affine transformation parameters 

139 sx = sy = scale 

140 cosine = math.cos(angle) 

141 sine = math.sin(angle) 

142 

143 a = cosine / sx 

144 b = -sine / sy 

145 # b = sine/sx 

146 c = 0 # Translation in x 

147 

148 d = sine / sx 

149 e = cosine / sy 

150 f = tr # Translation in y 

151 # d = -sine/sy 

152 # e = cosine/sy 

153 # f = 0 

154 

155 g = 0 

156 h = 0 

157 # h=tr 

158 i = 1 

159 

160 T = numpy.matrix([[a, b, c], [d, e, f], [g, h, i]]) 

161 Tinv = numpy.linalg.inv(T) 

162 Tinvtuple = ( 

163 Tinv[0, 0], 

164 Tinv[0, 1], 

165 Tinv[0, 2], 

166 Tinv[1, 0], 

167 Tinv[1, 1], 

168 Tinv[1, 2], 

169 ) 

170 

171 def _afftrans(img): 

172 """Applies the affine transform on the resulting image""" 

173 

174 t = Image.fromarray(img.astype("uint8")) 

175 w, h = t.size # pillow image is encoded w, h 

176 w += 2 * self.padding_width 

177 h += 2 * self.padding_width 

178 t = t.transform( 

179 (w, h), 

180 Image.AFFINE, 

181 Tinvtuple, 

182 resample=Image.BICUBIC, 

183 fill=self.padding_constant, 

184 ) 

185 

186 return numpy.array(t).astype(img.dtype) 

187 

188 return _afftrans(image), _afftrans(mask)