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
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-24 17:26 +0000
1#!/usr/bin/env python
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/>.
18import multiprocessing
19import os
21import numpy
22import progressbar
23import spam.deformation
24import spam.DIC
25import spam.helpers
27os.environ["OPENBLAS_NUM_THREADS"] = "1"
29numpy.seterr(all="ignore")
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".
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
60 - im2 : 2/3D numpy array
61 Deformed image
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)`
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
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
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
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
90 numberOfNodes = nodePositions.shape[0]
92 if processes is None:
93 processes = multiprocessing.cpu_count()
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()
102 error = numpy.zeros(numberOfNodes)
103 iterations = numpy.zeros(numberOfNodes)
104 returnStatus = numpy.zeros(numberOfNodes)
105 deltaPhiNorm = numpy.zeros(numberOfNodes)
107 # Bad to redefine this for every loop, so it's defined here, to be called by the pool
108 global _multiprocessingCorrelateOneNode
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:
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 )
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
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"]
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
174 finishedNodes = 0
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)
179 if skipNodesMask is not None:
180 nodesToCorrelate = nodesToCorrelate[numpy.logical_not(skipNodesMask)]
181 numberOfNodes = numpy.sum(numpy.logical_not(skipNodesMask))
183 print(f"\n\tStarting local dic for {numberOfNodes} nodes (with {processes} process{'es' if processes > 1 else ''})")
185 widgets = [progressbar.FormatLabel(""), " ", progressbar.Bar(), " ", progressbar.AdaptiveETA()]
186 pbar = progressbar.ProgressBar(widgets=widgets, maxval=numberOfNodes)
187 pbar.start()
188 finishedNodes = 0
190 with multiprocessing.Pool(processes=processes) as pool:
191 for returns in pool.imap_unordered(_multiprocessingCorrelateOneNode, nodesToCorrelate):
192 finishedNodes += 1
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]
205 pbar.finish()
207 print("\n")
209 return PhiFieldOut, returnStatus, error, iterations, deltaPhiNorm