Coverage for /usr/local/lib/python3.10/site-packages/spam/DIC/ldic.py: 92%

52 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-04-24 17:26 +0000

1#!/usr/bin/env python 

2 

3# Copyright (C) 2020 SPAM Contributors 

4# 

5# This program is free software: you can redistribute it and/or modify it 

6# under the terms of the GNU General Public License as published by the Free 

7# Software Foundation, either version 3 of the License, or (at your option) 

8# any later version. 

9# 

10# This program is distributed in the hope that it will be useful, but WITHOUT 

11# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 

12# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 

13# more details. 

14# 

15# You should have received a copy of the GNU General Public License along with 

16# this program. If not, see <http://www.gnu.org/licenses/>. 

17 

18import multiprocessing 

19import os 

20 

21import numpy 

22import progressbar 

23import spam.deformation 

24import spam.DIC 

25import spam.helpers 

26 

27os.environ["OPENBLAS_NUM_THREADS"] = "1" 

28 

29numpy.seterr(all="ignore") 

30 

31 

32def ldic( 

33 im1, 

34 im2, 

35 nodePositions, 

36 hws, 

37 skipNodesMask=None, # This will not work until we can recv returnStatus as input 

38 processes=None, 

39 im1mask=None, 

40 PhiField=None, # Will be modified in situ!! 

41 margin=[-3, 3, -3, 3, -3, 3], 

42 maskCoverage=0.5, 

43 greyThreshold=[-numpy.inf, numpy.inf], 

44 applyF="all", 

45 maxIterations=50, 

46 deltaPhiMin=0.001, 

47 PhiRigid=False, 

48 updateGradient=False, 

49 interpolationOrder=1, 

50): 

51 """ 

52 Function to perform local DIC/DVC (`i.e.`, running the spam.DIC.register function) 

53 correlating many 2D/3D boxes spread out typically on a regular grid of "nodes". 

54 

55 Parameters 

56 ---------- 

57 - im1 : 2/3D numpy array 

58 Reference image in which the nodes are defined, and from which the output Phi field is measured 

59 

60 - im2 : 2/3D numpy array 

61 Deformed image 

62 

63 - nodePositions : 2D numpy array of ints 

64 Nx2 or Nx3 matrix defining centres of boxes. 

65 This can be generated with `nodePositions, nodesDim = spam.DIC.makeGrid(im1.shape, nodeSpacing)` 

66 

67 - hws : 3-component numpy array of ints 

68 This defines the half-size of the correlation window, 

69 i.e., how many pixels to extract in Z, Y, X either side of the centre. 

70 Note: for 2D Z = 0 

71 

72 - skipNodes : 1D numpy array of bools, optional 

73 Vector of N bools which are true when nodes should be skipped. 

74 They will simply not be correlated, so ignore the outputs related to these nodes. 

75 If you have guesses for them, remember to merge them with the outputs for this function 

76 

77 - processes : int, optional 

78 Number of processes to run the ldic on, by default it's the number of detected threads on your machine 

79 

80 - for all other parameters see `spam.DIC.register()` 

81 """ 

82 # Detect unpadded 2D image first: 

83 if len(im1.shape) == 2: 

84 im1 = im1[numpy.newaxis, ...] 

85 if im1.shape[0] == 1: 

86 twoD = True 

87 else: 

88 twoD = False 

89 

90 numberOfNodes = nodePositions.shape[0] 

91 

92 if processes is None: 

93 processes = multiprocessing.cpu_count() 

94 

95 PhiFieldOut = numpy.zeros((numberOfNodes, 4, 4)) 

96 if PhiField is None: 

97 for node in range(numberOfNodes): 

98 PhiFieldOut[node] = numpy.eye(4) 

99 else: 

100 PhiFieldOut = PhiField.copy() 

101 

102 error = numpy.zeros(numberOfNodes) 

103 iterations = numpy.zeros(numberOfNodes) 

104 returnStatus = numpy.zeros(numberOfNodes) 

105 deltaPhiNorm = numpy.zeros(numberOfNodes) 

106 

107 # Bad to redefine this for every loop, so it's defined here, to be called by the pool 

108 global _multiprocessingCorrelateOneNode 

109 

110 # Bad to redefine this for every loop, so it's defined here, to be called by the pool 

111 def _multiprocessingCorrelateOneNode(nodeNumber): 

112 """ 

113 This function does a correlation at one point and returns: 

114 

115 Returns 

116 ------- 

117 List of: 

118 - nodeNumber 

119 - Phi 

120 - returnStatus 

121 - error 

122 - iterations 

123 - deltaPhiNorm 

124 """ 

125 PhiInit = PhiFieldOut[nodeNumber] 

126 if numpy.isfinite(PhiInit).sum() == 16: 

127 imagetteReturns = spam.DIC.getImagettes( 

128 im1, 

129 nodePositions[nodeNumber], 

130 hws, 

131 PhiInit.copy(), 

132 im2, 

133 margin, 

134 im1mask=im1mask, 

135 minMaskCoverage=maskCoverage, 

136 greyThreshold=greyThreshold, 

137 applyF="no", # Needs to be "no"? 

138 twoD=twoD, 

139 ) 

140 

141 if imagetteReturns["returnStatus"] == 1: 

142 # compute displacement that will be taken by the getImagettes 

143 initialDisplacement = numpy.round(PhiInit[0:3, 3]).astype(int) 

144 PhiInit[0:3, -1] -= initialDisplacement 

145 

146 registerReturns = spam.DIC.register( 

147 imagetteReturns["imagette1"], 

148 imagetteReturns["imagette2"], 

149 im1mask=imagetteReturns["imagette1mask"], 

150 PhiInit=PhiInit, # minus initial displacement above, which is in the search range and thus taken into account in imagette2 

151 margin=1, # see top of this file for compensation 

152 maxIterations=maxIterations, 

153 deltaPhiMin=deltaPhiMin, 

154 PhiRigid=PhiRigid, 

155 updateGradient=updateGradient, 

156 interpolationOrder=interpolationOrder, 

157 verbose=False, 

158 imShowProgress=False, 

159 ) 

160 goodPhi = registerReturns["Phi"] 

161 goodPhi[0:3, -1] += initialDisplacement 

162 return nodeNumber, goodPhi, registerReturns["returnStatus"], registerReturns["error"], registerReturns["iterations"], registerReturns["deltaPhiNorm"] 

163 

164 else: 

165 badPhi = numpy.eye(4) 

166 badPhi[0:3, 3] = numpy.nan 

167 return nodeNumber, badPhi, imagetteReturns["returnStatus"], numpy.inf, 0, numpy.inf 

168 else: 

169 # Phi has nans or infs 

170 badPhi = numpy.eye(4) 

171 badPhi[0:3, 3] = numpy.nan 

172 return nodeNumber, badPhi, -7, numpy.inf, 0, numpy.inf 

173 

174 finishedNodes = 0 

175 

176 # GP: Adding the skip function. TODO: returnStatus needs to be fed as an input to preserve it, otherwise is returned as 0. 

177 nodesToCorrelate = numpy.arange(0, numberOfNodes) 

178 

179 if skipNodesMask is not None: 

180 nodesToCorrelate = nodesToCorrelate[numpy.logical_not(skipNodesMask)] 

181 numberOfNodes = numpy.sum(numpy.logical_not(skipNodesMask)) 

182 

183 print(f"\n\tStarting local dic for {numberOfNodes} nodes (with {processes} process{'es' if processes > 1 else ''})") 

184 

185 widgets = [progressbar.FormatLabel(""), " ", progressbar.Bar(), " ", progressbar.AdaptiveETA()] 

186 pbar = progressbar.ProgressBar(widgets=widgets, maxval=numberOfNodes) 

187 pbar.start() 

188 finishedNodes = 0 

189 

190 with multiprocessing.Pool(processes=processes) as pool: 

191 for returns in pool.imap_unordered(_multiprocessingCorrelateOneNode, nodesToCorrelate): 

192 finishedNodes += 1 

193 

194 # Update progres bar if point is not skipped 

195 if returns[2] > 0: 

196 widgets[0] = progressbar.FormatLabel(f" it={returns[4]:0>3d} dPhiNorm={returns[5]:0>6.4f} rs={returns[2]:+1d} ") 

197 pbar.update(finishedNodes) 

198 nodeNumber = returns[0] 

199 PhiFieldOut[nodeNumber] = returns[1] 

200 returnStatus[nodeNumber] = returns[2] 

201 error[nodeNumber] = returns[3] 

202 iterations[nodeNumber] = returns[4] 

203 deltaPhiNorm[nodeNumber] = returns[5] 

204 

205 pbar.finish() 

206 

207 print("\n") 

208 

209 return PhiFieldOut, returnStatus, error, iterations, deltaPhiNorm