py4dgeo 0.7.0__cp313-cp313-macosx_14_0_arm64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- _py4dgeo.cpython-313-darwin.so +0 -0
- py4dgeo/.dylibs/libomp.dylib +0 -0
- py4dgeo/UpdateableZipFile.py +81 -0
- py4dgeo/__init__.py +32 -0
- py4dgeo/cloudcompare.py +32 -0
- py4dgeo/epoch.py +814 -0
- py4dgeo/fallback.py +159 -0
- py4dgeo/logger.py +77 -0
- py4dgeo/m3c2.py +244 -0
- py4dgeo/m3c2ep.py +855 -0
- py4dgeo/pbm3c2.py +3870 -0
- py4dgeo/py4dgeo_python.cpp +487 -0
- py4dgeo/registration.py +474 -0
- py4dgeo/segmentation.py +1280 -0
- py4dgeo/util.py +263 -0
- py4dgeo-0.7.0.dist-info/METADATA +200 -0
- py4dgeo-0.7.0.dist-info/RECORD +21 -0
- py4dgeo-0.7.0.dist-info/WHEEL +5 -0
- py4dgeo-0.7.0.dist-info/entry_points.txt +3 -0
- py4dgeo-0.7.0.dist-info/licenses/COPYING.md +17 -0
- py4dgeo-0.7.0.dist-info/licenses/LICENSE.md +5 -0
py4dgeo/registration.py
ADDED
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
from py4dgeo.util import Py4DGeoError
|
|
2
|
+
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
import dataclasses
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
import _py4dgeo
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclasses.dataclass(frozen=True)
|
|
11
|
+
class Transformation:
|
|
12
|
+
"""A transformation that can be applied to a point cloud"""
|
|
13
|
+
|
|
14
|
+
affine_transformation: np.ndarray
|
|
15
|
+
reduction_point: np.ndarray
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _fit_transform(A, B, reduction_point=None):
|
|
19
|
+
"""Find a transformation that fits two point clouds onto each other"""
|
|
20
|
+
|
|
21
|
+
assert A.shape == B.shape
|
|
22
|
+
|
|
23
|
+
# get number of dimensions
|
|
24
|
+
m = A.shape[1]
|
|
25
|
+
|
|
26
|
+
centroid_A = np.mean(A, axis=0)
|
|
27
|
+
centroid_B = np.mean(B, axis=0)
|
|
28
|
+
|
|
29
|
+
# Apply the reduction_point if provided
|
|
30
|
+
if reduction_point is not None:
|
|
31
|
+
centroid_A -= reduction_point
|
|
32
|
+
centroid_B -= reduction_point
|
|
33
|
+
|
|
34
|
+
AA = A - centroid_A
|
|
35
|
+
BB = B - centroid_B
|
|
36
|
+
|
|
37
|
+
H = np.dot(AA.T, BB)
|
|
38
|
+
U, _, Vt = np.linalg.svd(H)
|
|
39
|
+
R = np.dot(Vt.T, U.T)
|
|
40
|
+
t = centroid_B.T - np.dot(R, centroid_A.T)
|
|
41
|
+
# special reflection case
|
|
42
|
+
if np.linalg.det(R) < 0:
|
|
43
|
+
Vt[2, :] *= -1
|
|
44
|
+
R = np.dot(Vt.T, U.T)
|
|
45
|
+
|
|
46
|
+
# homogeneous transformation
|
|
47
|
+
T = np.identity(4)
|
|
48
|
+
T[:3, :3] = R
|
|
49
|
+
T[:3, 3] = t
|
|
50
|
+
return T
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def iterative_closest_point(
|
|
54
|
+
reference_epoch, epoch, max_iterations=50, tolerance=0.00001, reduction_point=None
|
|
55
|
+
):
|
|
56
|
+
"""Perform an Iterative Closest Point algorithm (ICP)
|
|
57
|
+
|
|
58
|
+
:param reference_epoch:
|
|
59
|
+
The reference epoch to match with.
|
|
60
|
+
:type reference_epoch: py4dgeo.Epoch
|
|
61
|
+
:param epoch:
|
|
62
|
+
The epoch to be transformed to the reference epoch
|
|
63
|
+
:type epoch: py4dgeo.Epoch
|
|
64
|
+
:param max_iterations:
|
|
65
|
+
The maximum number of iterations to be performed in the ICP algorithm
|
|
66
|
+
:type max_iterations: int
|
|
67
|
+
:param tolerance:
|
|
68
|
+
The tolerance criterium used to terminate ICP iteration.
|
|
69
|
+
:type tolerance: float
|
|
70
|
+
:param reduction_point:
|
|
71
|
+
A translation vector to apply before applying rotation and scaling.
|
|
72
|
+
This is used to increase the numerical accuracy of transformation.
|
|
73
|
+
:type reduction_point: np.ndarray
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
# Ensure that reference_epoch has its KDTree built
|
|
77
|
+
if reference_epoch.kdtree.leaf_parameter() == 0:
|
|
78
|
+
reference_epoch.build_kdtree()
|
|
79
|
+
|
|
80
|
+
# Apply the default for the registration point
|
|
81
|
+
if reduction_point is None:
|
|
82
|
+
reduction_point = np.array([0, 0, 0])
|
|
83
|
+
|
|
84
|
+
# Make a copy of the cloud to be transformed.
|
|
85
|
+
cloud = epoch.cloud.copy()
|
|
86
|
+
prev_error = 0
|
|
87
|
+
|
|
88
|
+
for _ in range(max_iterations):
|
|
89
|
+
neighbor_arrays = np.asarray(reference_epoch.kdtree.nearest_neighbors(cloud, 1))
|
|
90
|
+
indices, distances = np.split(neighbor_arrays, 2, axis=0)
|
|
91
|
+
|
|
92
|
+
indices = np.squeeze(indices.astype(int))
|
|
93
|
+
distances = np.squeeze(distances)
|
|
94
|
+
|
|
95
|
+
# Calculate a transform and apply it
|
|
96
|
+
T = _fit_transform(
|
|
97
|
+
cloud, reference_epoch.cloud[indices, :], reduction_point=reduction_point
|
|
98
|
+
)
|
|
99
|
+
_py4dgeo.transform_pointcloud_inplace(
|
|
100
|
+
cloud, T, reduction_point, np.empty((1, 3))
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Determine convergence
|
|
104
|
+
mean_error = np.mean(np.sqrt(distances))
|
|
105
|
+
|
|
106
|
+
if np.abs(prev_error - mean_error) < tolerance:
|
|
107
|
+
break
|
|
108
|
+
prev_error = mean_error
|
|
109
|
+
|
|
110
|
+
return Transformation(
|
|
111
|
+
affine_transformation=_fit_transform(epoch.cloud, cloud),
|
|
112
|
+
reduction_point=reduction_point,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def point_to_plane_icp(
|
|
117
|
+
reference_epoch, epoch, max_iterations=50, tolerance=0.00001, reduction_point=None
|
|
118
|
+
):
|
|
119
|
+
"""Perform a point to plane Iterative Closest Point algorithm (ICP), based on Gauss-Newton method for computing the least squares solution
|
|
120
|
+
|
|
121
|
+
:param reference_epoch:
|
|
122
|
+
The reference epoch to match with. This epoch has to have calculated normals.
|
|
123
|
+
:type reference_epoch: py4dgeo.Epoch
|
|
124
|
+
:param epoch:
|
|
125
|
+
The epoch to be transformed to the reference epoch
|
|
126
|
+
:type epoch: py4dgeo.Epoch
|
|
127
|
+
:param max_iterations:
|
|
128
|
+
The maximum number of iterations to be performed in the ICP algorithm
|
|
129
|
+
:type max_iterations: int
|
|
130
|
+
:param tolerance:
|
|
131
|
+
The tolerance criterium used to terminate ICP iteration.
|
|
132
|
+
:type tolerance: float
|
|
133
|
+
:param reduction_point:
|
|
134
|
+
A translation vector to apply before applying rotation and scaling.
|
|
135
|
+
This is used to increase the numerical accuracy of transformation.
|
|
136
|
+
:type reduction_point: np.ndarray
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
from py4dgeo.epoch import Epoch
|
|
140
|
+
|
|
141
|
+
# Ensure that Epoch has calculated normals
|
|
142
|
+
if reference_epoch.normals is None:
|
|
143
|
+
raise Py4DGeoError(
|
|
144
|
+
"Normals for this Reference Epoch have not been calculated! Please use Epoch.calculate_normals or load externally calculated normals."
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# Ensure that reference_epoch has its KDTree built
|
|
148
|
+
if reference_epoch.kdtree.leaf_parameter() == 0:
|
|
149
|
+
reference_epoch.build_kdtree()
|
|
150
|
+
|
|
151
|
+
# Apply the default for the registration point
|
|
152
|
+
if reduction_point is None:
|
|
153
|
+
reduction_point = np.array([0, 0, 0])
|
|
154
|
+
|
|
155
|
+
# Make a copy of the cloud to be transformed.
|
|
156
|
+
trans_epoch = epoch.copy()
|
|
157
|
+
|
|
158
|
+
prev_error = 0
|
|
159
|
+
for _ in range(max_iterations):
|
|
160
|
+
neighbor_arrays = np.asarray(
|
|
161
|
+
reference_epoch.kdtree.nearest_neighbors(trans_epoch.cloud, 1)
|
|
162
|
+
)
|
|
163
|
+
indices, distances = np.split(neighbor_arrays, 2, axis=0)
|
|
164
|
+
|
|
165
|
+
indices = np.squeeze(indices.astype(int))
|
|
166
|
+
distances = np.squeeze(distances)
|
|
167
|
+
|
|
168
|
+
# Calculate a transform and apply it
|
|
169
|
+
T = _py4dgeo.fit_transform_GN(
|
|
170
|
+
trans_epoch.cloud,
|
|
171
|
+
reference_epoch.cloud[indices, :],
|
|
172
|
+
reference_epoch.normals[indices, :],
|
|
173
|
+
)
|
|
174
|
+
trans_epoch.transform(
|
|
175
|
+
Transformation(affine_transformation=T, reduction_point=reduction_point)
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# Determine convergence
|
|
179
|
+
mean_error = np.mean(np.sqrt(distances))
|
|
180
|
+
if np.abs(prev_error - mean_error) < tolerance:
|
|
181
|
+
break
|
|
182
|
+
prev_error = mean_error
|
|
183
|
+
|
|
184
|
+
return Transformation(
|
|
185
|
+
affine_transformation=_py4dgeo.fit_transform_GN(
|
|
186
|
+
epoch.cloud,
|
|
187
|
+
trans_epoch.cloud,
|
|
188
|
+
trans_epoch.normals,
|
|
189
|
+
),
|
|
190
|
+
reduction_point=reduction_point,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def calculate_bounding_box(point_cloud):
|
|
195
|
+
"""
|
|
196
|
+
Calculate the bounding box of a point cloud.
|
|
197
|
+
|
|
198
|
+
Parameters:
|
|
199
|
+
- point_cloud: NumPy array with shape (N, 3), where N is the number of points.
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
- min_bound: 1D array representing the minimum coordinates of the bounding box.
|
|
203
|
+
- max_bound: 1D array representing the maximum coordinates of the bounding box.
|
|
204
|
+
"""
|
|
205
|
+
min_bound = np.min(point_cloud, axis=0)
|
|
206
|
+
max_bound = np.max(point_cloud, axis=0)
|
|
207
|
+
|
|
208
|
+
return min_bound, max_bound
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def calculate_bounding_box_change(
|
|
212
|
+
bounding_box_min, bounding_box_max, transformation_matrix
|
|
213
|
+
):
|
|
214
|
+
"""Calculate the change in kdtree bounding box corners after applying a transformation matrix.
|
|
215
|
+
Parameters:
|
|
216
|
+
- bounding_box_min: 1D array representing the minimum coordinates of the bounding box.
|
|
217
|
+
- bounding_box_max: 1D array representing the maximum coordinates of the bounding box.
|
|
218
|
+
- transformation_matrix: 2D array representing the transformation matrix.
|
|
219
|
+
Returns:
|
|
220
|
+
- max_change: The maximum change in the bounding box corners.
|
|
221
|
+
"""
|
|
222
|
+
|
|
223
|
+
# Convert bounding box to homogeneous coordinates
|
|
224
|
+
bounding_box_min_homogeneous = np.concatenate((bounding_box_min, [1]))
|
|
225
|
+
bounding_box_max_homogeneous = np.concatenate((bounding_box_max, [1]))
|
|
226
|
+
bounding_box_min_homogeneous = np.reshape(bounding_box_min_homogeneous, (4, 1))
|
|
227
|
+
bounding_box_max_homogeneous = np.reshape(bounding_box_max_homogeneous, (4, 1))
|
|
228
|
+
|
|
229
|
+
# Calculate the change in bounding box corners
|
|
230
|
+
bb_c2p1 = np.dot(transformation_matrix, bounding_box_min_homogeneous)
|
|
231
|
+
bb_c2p2 = np.dot(transformation_matrix, bounding_box_max_homogeneous)
|
|
232
|
+
|
|
233
|
+
dif_bb_pmin = np.sum(np.abs(bb_c2p1[:3] - bounding_box_min_homogeneous[:3]))
|
|
234
|
+
dif_bb_pmax = np.sum(np.abs(bb_c2p2[:3] - bounding_box_max_homogeneous[:3]))
|
|
235
|
+
|
|
236
|
+
return max(dif_bb_pmin, dif_bb_pmax)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def calculate_dis_threshold(epoch1, epoch2):
|
|
240
|
+
"""Calculate the distance threshold for the next iteration of the registration method
|
|
241
|
+
Parameters:
|
|
242
|
+
- epoch1: The reference epoch.
|
|
243
|
+
- epoch2: Stable points of epoch.
|
|
244
|
+
Returns:
|
|
245
|
+
- dis_threshold: The distance threshold.
|
|
246
|
+
"""
|
|
247
|
+
neighbor_arrays = np.asarray(epoch1.kdtree.nearest_neighbors(epoch2.cloud, 1))
|
|
248
|
+
indices, distances = np.split(neighbor_arrays, 2, axis=0)
|
|
249
|
+
distances = np.squeeze(distances)
|
|
250
|
+
|
|
251
|
+
if indices.size > 0:
|
|
252
|
+
# Calculate mean distance
|
|
253
|
+
mean_dis = np.mean(np.sqrt(distances))
|
|
254
|
+
|
|
255
|
+
# Calculate standard deviation
|
|
256
|
+
std_dis = np.sqrt(np.mean((mean_dis - distances) ** 2))
|
|
257
|
+
|
|
258
|
+
dis_threshold = mean_dis + 1.0 * std_dis
|
|
259
|
+
|
|
260
|
+
return dis_threshold
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def icp_with_stable_areas(
|
|
264
|
+
reference_epoch,
|
|
265
|
+
epoch,
|
|
266
|
+
initial_distance_threshold,
|
|
267
|
+
level_of_detection,
|
|
268
|
+
reference_supervoxel_resolution,
|
|
269
|
+
supervoxel_resolution,
|
|
270
|
+
min_svp_num=10,
|
|
271
|
+
reduction_point=None,
|
|
272
|
+
):
|
|
273
|
+
"""Perform a registration method
|
|
274
|
+
|
|
275
|
+
:param reference_epoch:
|
|
276
|
+
The reference epoch to match with. This epoch has to have calculated normals.
|
|
277
|
+
:type reference_epoch: py4dgeo.Epoch
|
|
278
|
+
:param epoch:
|
|
279
|
+
The epoch to be transformed to the reference epoch
|
|
280
|
+
:type epoch: py4dgeo.Epoch
|
|
281
|
+
:param initial_distance_threshold:
|
|
282
|
+
The upper boundary of the distance threshold in the iteration. It can be (1) an empirical value manually set by the user according to the approximate accuracy of coarse registration,
|
|
283
|
+
or (2) calculated by the mean and standard of the nearest neighbor distances of all points.
|
|
284
|
+
:type initial_distance_threshold: float
|
|
285
|
+
:param level_of_detection:
|
|
286
|
+
The lower boundary (minimum) of the distance threshold in the iteration.
|
|
287
|
+
It can be (1) an empirical value manually set by the user according to the approximate uncertainty of laser scanning measurements in different scanning configurations and scenarios
|
|
288
|
+
(e.g., 1 cm for TLS point clouds in short distance and 4 cm in long distance, 8 cm for ALS point clouds, etc.),
|
|
289
|
+
or (2) calculated by estimating the standard deviation from local modeling (e.g., using the level of detection in M3C2 or M3C2-EP calculations).
|
|
290
|
+
:type level_of_detection: float
|
|
291
|
+
:param reference_supervoxel_resolution:
|
|
292
|
+
The approximate size of generated supervoxels for the reference epoch.
|
|
293
|
+
It can be (1) an empirical value manually set by the user according to different surface geometries and scanning distance (e.g., 2-10 cm for indoor scenes, 1-3 m for landslide surface),
|
|
294
|
+
or (2) calculated by 10-20 times the average point spacing (original resolution of point clouds). In both cases, the number of points in each supervoxel should be at least 10 (i.e., minSVPnum = 10).
|
|
295
|
+
:type reference_supervoxel_resolution: float
|
|
296
|
+
:param supervoxel_resolution:
|
|
297
|
+
The same as `reference_supervoxel_resolution`, but for a different epoch.
|
|
298
|
+
:type supervoxel_resolution: float
|
|
299
|
+
:param min_svp_num:
|
|
300
|
+
Minimum number of points for supervoxels to be taken into account in further calculations.
|
|
301
|
+
:type min_svp_num: int
|
|
302
|
+
:param reduction_point:
|
|
303
|
+
A translation vector to apply before applying rotation and scaling.
|
|
304
|
+
This is used to increase the numerical accuracy of transformation.
|
|
305
|
+
:type reduction_point: np.ndarray
|
|
306
|
+
|
|
307
|
+
"""
|
|
308
|
+
|
|
309
|
+
from py4dgeo.epoch import as_epoch
|
|
310
|
+
|
|
311
|
+
# Ensure that reference_epoch has its KDTree build
|
|
312
|
+
if reference_epoch.kdtree.leaf_parameter() == 0:
|
|
313
|
+
reference_epoch.build_kdtree()
|
|
314
|
+
|
|
315
|
+
# Ensure that epoch has its KDTree build
|
|
316
|
+
if epoch.kdtree.leaf_parameter() == 0:
|
|
317
|
+
epoch.build_kdtree()
|
|
318
|
+
|
|
319
|
+
# Ensure that Epoch has calculated normals
|
|
320
|
+
# Ensure that Epoch has calculated normals
|
|
321
|
+
if reference_epoch.normals is None:
|
|
322
|
+
raise Py4DGeoError(
|
|
323
|
+
"Normals for this Reference Epoch have not been calculated! Please use Epoch.calculate_normals or load externally calculated normals."
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
# Ensure that Epoch has calculated normals
|
|
327
|
+
|
|
328
|
+
# Ensure that Epoch has calculated normals
|
|
329
|
+
if epoch.normals is None:
|
|
330
|
+
raise Py4DGeoError(
|
|
331
|
+
"Normals for this Epoch have not been calculated! Please use Epoch.calculate_normals or load externally calculated normals."
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
# Apply the default for the registration point
|
|
335
|
+
if reduction_point is None:
|
|
336
|
+
reduction_point = np.array([0, 0, 0])
|
|
337
|
+
|
|
338
|
+
if initial_distance_threshold <= level_of_detection:
|
|
339
|
+
initial_distance_threshold = level_of_detection
|
|
340
|
+
|
|
341
|
+
transMatFinal = np.identity(4) # Identity matrix for initial transMatFinal
|
|
342
|
+
stage3 = stage4 = 0
|
|
343
|
+
epoch_copy = epoch.copy() # Create copy of epoch for applying transformation
|
|
344
|
+
|
|
345
|
+
k = 50 # Number of nearest neighbors to consider in supervoxel segmentation
|
|
346
|
+
|
|
347
|
+
clouds_pc1, _, centroids_pc1, _ = _py4dgeo.segment_pc_in_supervoxels(
|
|
348
|
+
reference_epoch,
|
|
349
|
+
reference_epoch.kdtree,
|
|
350
|
+
reference_epoch.normals,
|
|
351
|
+
reference_supervoxel_resolution,
|
|
352
|
+
k,
|
|
353
|
+
min_svp_num,
|
|
354
|
+
)
|
|
355
|
+
(
|
|
356
|
+
clouds_pc2,
|
|
357
|
+
normals2,
|
|
358
|
+
centroids_pc2,
|
|
359
|
+
boundary_points_pc2,
|
|
360
|
+
) = _py4dgeo.segment_pc_in_supervoxels(
|
|
361
|
+
epoch, epoch.kdtree, epoch.normals, supervoxel_resolution, k, min_svp_num
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
centroids_pc1 = as_epoch(np.array(centroids_pc1))
|
|
365
|
+
centroids_pc1.build_kdtree()
|
|
366
|
+
centroids_pc2 = np.array(centroids_pc2)
|
|
367
|
+
boundary_points_pc2 = np.concatenate(boundary_points_pc2, axis=0)
|
|
368
|
+
|
|
369
|
+
_, reference_distances = np.split(
|
|
370
|
+
np.asarray(reference_epoch.kdtree.nearest_neighbors(reference_epoch.cloud, 2)),
|
|
371
|
+
2,
|
|
372
|
+
axis=0,
|
|
373
|
+
)
|
|
374
|
+
basicRes = np.mean(np.squeeze(reference_distances))
|
|
375
|
+
dis_threshold = initial_distance_threshold
|
|
376
|
+
|
|
377
|
+
while stage4 == 0:
|
|
378
|
+
cor_dist_ct = _py4dgeo.compute_correspondence_distances(
|
|
379
|
+
centroids_pc1, centroids_pc2, clouds_pc1, len(reference_epoch.cloud)
|
|
380
|
+
)
|
|
381
|
+
# Calculation BP2-CT1
|
|
382
|
+
cor_dist_bp = _py4dgeo.compute_correspondence_distances(
|
|
383
|
+
centroids_pc1,
|
|
384
|
+
boundary_points_pc2,
|
|
385
|
+
clouds_pc1,
|
|
386
|
+
len(reference_epoch.cloud),
|
|
387
|
+
)
|
|
388
|
+
# calculation BP2- CP1
|
|
389
|
+
cor_dist_pc = _py4dgeo.compute_correspondence_distances(
|
|
390
|
+
reference_epoch,
|
|
391
|
+
boundary_points_pc2,
|
|
392
|
+
clouds_pc1,
|
|
393
|
+
len(reference_epoch.cloud),
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
stablePC2 = [] # Stable supervoxels
|
|
397
|
+
normPC2 = [] # Stable supervoxel's normals
|
|
398
|
+
|
|
399
|
+
dt_point = dis_threshold + 2 * basicRes
|
|
400
|
+
|
|
401
|
+
for i in range(len(centroids_pc2)):
|
|
402
|
+
if cor_dist_ct[i] < dis_threshold and all(
|
|
403
|
+
cor_dist_bp[j + 6 * i] < dis_threshold
|
|
404
|
+
and cor_dist_pc[j + 6 * i] < dt_point
|
|
405
|
+
for j in range(6)
|
|
406
|
+
):
|
|
407
|
+
stablePC2.append(clouds_pc2[i])
|
|
408
|
+
normPC2.append(normals2[i])
|
|
409
|
+
|
|
410
|
+
# Handle empty stablePC2
|
|
411
|
+
if len(stablePC2) == 0:
|
|
412
|
+
raise Py4DGeoError(
|
|
413
|
+
"No stable supervoxels found! Please adjust the parameters."
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
stablePC2 = np.vstack(stablePC2)
|
|
417
|
+
stablePC2 = as_epoch(stablePC2)
|
|
418
|
+
normPC2 = np.vstack(normPC2)
|
|
419
|
+
stablePC2.normals_attachment(normPC2)
|
|
420
|
+
trans_mat_cur_obj = point_to_plane_icp(
|
|
421
|
+
reference_epoch,
|
|
422
|
+
stablePC2,
|
|
423
|
+
max_iterations=50,
|
|
424
|
+
tolerance=0.00001,
|
|
425
|
+
reduction_point=reduction_point,
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
trans_mat_cur = trans_mat_cur_obj.affine_transformation
|
|
429
|
+
|
|
430
|
+
# BB
|
|
431
|
+
initial_min_bound, initial_max_bound = calculate_bounding_box(epoch_copy.cloud)
|
|
432
|
+
max_bb_change = calculate_bounding_box_change(
|
|
433
|
+
initial_min_bound, initial_max_bound, trans_mat_cur
|
|
434
|
+
)
|
|
435
|
+
# update DT
|
|
436
|
+
if stage3 == 0 and max_bb_change < 2 * level_of_detection:
|
|
437
|
+
stage3 = 1
|
|
438
|
+
elif dis_threshold == level_of_detection:
|
|
439
|
+
stage4 = 1
|
|
440
|
+
|
|
441
|
+
if stage3 == 0:
|
|
442
|
+
dis_threshold = calculate_dis_threshold(reference_epoch, stablePC2)
|
|
443
|
+
if dis_threshold <= level_of_detection:
|
|
444
|
+
dis_threshold = level_of_detection
|
|
445
|
+
|
|
446
|
+
if stage3 == 1 and stage4 == 0:
|
|
447
|
+
dis_threshold = 0.8 * dis_threshold
|
|
448
|
+
if dis_threshold <= level_of_detection:
|
|
449
|
+
dis_threshold = level_of_detection
|
|
450
|
+
|
|
451
|
+
# update values and apply changes
|
|
452
|
+
# Apply the transformation to the epoch
|
|
453
|
+
epoch_copy.transform(
|
|
454
|
+
Transformation(
|
|
455
|
+
affine_transformation=trans_mat_cur, reduction_point=reduction_point
|
|
456
|
+
)
|
|
457
|
+
)
|
|
458
|
+
_py4dgeo.transform_pointcloud_inplace(
|
|
459
|
+
centroids_pc2, trans_mat_cur, reduction_point, np.empty((1, 3))
|
|
460
|
+
)
|
|
461
|
+
_py4dgeo.transform_pointcloud_inplace(
|
|
462
|
+
boundary_points_pc2, trans_mat_cur, reduction_point, np.empty((1, 3))
|
|
463
|
+
)
|
|
464
|
+
for i in range(len(clouds_pc2)):
|
|
465
|
+
_py4dgeo.transform_pointcloud_inplace(
|
|
466
|
+
clouds_pc2[i], trans_mat_cur, reduction_point, np.empty((1, 3))
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
transMatFinal = trans_mat_cur @ transMatFinal
|
|
470
|
+
|
|
471
|
+
return Transformation(
|
|
472
|
+
affine_transformation=transMatFinal,
|
|
473
|
+
reduction_point=reduction_point,
|
|
474
|
+
)
|