py4dgeo 0.7.0__cp313-cp313-win_amd64.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.cp313-win_amd64.pyd +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 +20 -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
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
#include <pybind11/eigen.h>
|
|
2
|
+
#include <pybind11/functional.h>
|
|
3
|
+
#include <pybind11/numpy.h>
|
|
4
|
+
#include <pybind11/pybind11.h>
|
|
5
|
+
#include <pybind11/stl.h>
|
|
6
|
+
|
|
7
|
+
#ifdef PY4DGEO_WITH_OPENMP
|
|
8
|
+
#include <omp.h>
|
|
9
|
+
#endif
|
|
10
|
+
|
|
11
|
+
#include "py4dgeo/compute.hpp"
|
|
12
|
+
#include "py4dgeo/epoch.hpp"
|
|
13
|
+
#include "py4dgeo/kdtree.hpp"
|
|
14
|
+
#include "py4dgeo/py4dgeo.hpp"
|
|
15
|
+
#include "py4dgeo/pybind11_numpy_interop.hpp"
|
|
16
|
+
#include "py4dgeo/registration.hpp"
|
|
17
|
+
#include "py4dgeo/segmentation.hpp"
|
|
18
|
+
|
|
19
|
+
#include <fstream>
|
|
20
|
+
#include <sstream>
|
|
21
|
+
#include <string>
|
|
22
|
+
#include <tuple>
|
|
23
|
+
|
|
24
|
+
namespace py = pybind11;
|
|
25
|
+
|
|
26
|
+
namespace py4dgeo {
|
|
27
|
+
|
|
28
|
+
PYBIND11_MODULE(_py4dgeo, m)
|
|
29
|
+
{
|
|
30
|
+
m.doc() = "Python Bindings for py4dgeo";
|
|
31
|
+
|
|
32
|
+
// The enum class for our memory policy
|
|
33
|
+
py::enum_<MemoryPolicy>(m, "MemoryPolicy", py::arithmetic())
|
|
34
|
+
.value("STRICT", MemoryPolicy::STRICT)
|
|
35
|
+
.value("MINIMAL", MemoryPolicy::MINIMAL)
|
|
36
|
+
.value("COREPOINTS", MemoryPolicy::COREPOINTS)
|
|
37
|
+
.value("RELAXED", MemoryPolicy::RELAXED)
|
|
38
|
+
.export_values();
|
|
39
|
+
|
|
40
|
+
// Register a numpy structured type for uncertainty calculation. This allows
|
|
41
|
+
// us to allocate memory in C++ and expose it as a structured numpy array in
|
|
42
|
+
// Python. The given names will be usable in Python.
|
|
43
|
+
PYBIND11_NUMPY_DTYPE(DistanceUncertainty,
|
|
44
|
+
lodetection,
|
|
45
|
+
spread1,
|
|
46
|
+
num_samples1,
|
|
47
|
+
spread2,
|
|
48
|
+
num_samples2);
|
|
49
|
+
|
|
50
|
+
// Also expose the DistanceUncertainty data structure in Python, so that
|
|
51
|
+
// Python fallbacks can use it directly to define their result.
|
|
52
|
+
py::class_<DistanceUncertainty> unc(m, "DistanceUncertainty");
|
|
53
|
+
unc.def(py::init<double, double, IndexType, double, IndexType>(),
|
|
54
|
+
py::arg("lodetection") = 0.0,
|
|
55
|
+
py::arg("spread1") = 0.0,
|
|
56
|
+
py::arg("num_samples1") = 0,
|
|
57
|
+
py::arg("spread2") = 0.0,
|
|
58
|
+
py::arg("num_samples2") = 0);
|
|
59
|
+
|
|
60
|
+
// The epoch class
|
|
61
|
+
py::class_<Epoch> epoch(m, "Epoch");
|
|
62
|
+
|
|
63
|
+
// Initializing with a numpy array prevents the numpy array from being
|
|
64
|
+
// garbage collected as long as the Epoch object is alive
|
|
65
|
+
epoch.def(py::init<EigenPointCloudRef>(), py::keep_alive<1, 2>());
|
|
66
|
+
|
|
67
|
+
// We can directly access the point cloud and the kdtree
|
|
68
|
+
epoch.def_readwrite("_cloud", &Epoch::cloud);
|
|
69
|
+
epoch.def_readwrite("_kdtree", &Epoch::kdtree);
|
|
70
|
+
|
|
71
|
+
// Pickling support for the Epoch class
|
|
72
|
+
epoch.def(py::pickle(
|
|
73
|
+
[](const Epoch& self) {
|
|
74
|
+
// Serialize into in-memory stream
|
|
75
|
+
std::stringstream buf;
|
|
76
|
+
self.to_stream(buf);
|
|
77
|
+
return py::bytes(buf.str());
|
|
78
|
+
},
|
|
79
|
+
[](const py::bytes& data) {
|
|
80
|
+
std::stringstream buf(data.cast<std::string>());
|
|
81
|
+
return Epoch::from_stream(buf);
|
|
82
|
+
}));
|
|
83
|
+
|
|
84
|
+
// Expose the KDTree class
|
|
85
|
+
py::class_<KDTree> kdtree(m, "KDTree", py::buffer_protocol());
|
|
86
|
+
|
|
87
|
+
// Map __init__ to constructor
|
|
88
|
+
kdtree.def(py::init<>(&KDTree::create));
|
|
89
|
+
|
|
90
|
+
// Allow updating KDTree from a given file
|
|
91
|
+
kdtree.def("load_index", [](KDTree& self, std::string filename) {
|
|
92
|
+
std::ifstream stream(filename, std::ios::binary | std::ios::in);
|
|
93
|
+
self.loadIndex(stream);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Allow dumping KDTree to a file
|
|
97
|
+
kdtree.def("save_index", [](const KDTree& self, std::string filename) {
|
|
98
|
+
std::ofstream stream(filename, std::ios::binary | std::ios::out);
|
|
99
|
+
self.saveIndex(stream);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Allow building the KDTree structure
|
|
103
|
+
kdtree.def(
|
|
104
|
+
"build_tree", &KDTree::build_tree, "Trigger building the search tree");
|
|
105
|
+
|
|
106
|
+
// Allow invalidating the KDTree structure
|
|
107
|
+
kdtree.def("invalidate", &KDTree::invalidate, "Invalidate the search tree");
|
|
108
|
+
|
|
109
|
+
// Give access to the leaf parameter that the tree has been built with
|
|
110
|
+
kdtree.def("leaf_parameter",
|
|
111
|
+
&KDTree::get_leaf_parameter,
|
|
112
|
+
"Retrieve the leaf parameter that the tree has been built with.");
|
|
113
|
+
|
|
114
|
+
// Add all the radius search methods
|
|
115
|
+
kdtree.def(
|
|
116
|
+
"radius_search",
|
|
117
|
+
[](const KDTree& self, py::array_t<double> qp, double radius) {
|
|
118
|
+
// Get a pointer for the query point
|
|
119
|
+
auto ptr = static_cast<const double*>(qp.request().ptr);
|
|
120
|
+
|
|
121
|
+
KDTree::RadiusSearchResult result;
|
|
122
|
+
self.radius_search(ptr, radius, result);
|
|
123
|
+
|
|
124
|
+
return as_pyarray(std::move(result));
|
|
125
|
+
},
|
|
126
|
+
"Search point in given radius!");
|
|
127
|
+
|
|
128
|
+
kdtree.def(
|
|
129
|
+
"nearest_neighbors",
|
|
130
|
+
[](const KDTree& self, EigenPointCloudConstRef cloud, int k) {
|
|
131
|
+
KDTree::NearestNeighborsDistanceResult result;
|
|
132
|
+
self.nearest_neighbors_with_distances(cloud, result, k);
|
|
133
|
+
|
|
134
|
+
py::array_t<long int> indices_array(result.size());
|
|
135
|
+
py::array_t<double> distances_array(result.size());
|
|
136
|
+
|
|
137
|
+
auto indices_array_ptr = indices_array.mutable_data();
|
|
138
|
+
auto distances_array_ptr = distances_array.mutable_data();
|
|
139
|
+
|
|
140
|
+
for (size_t i = 0; i < result.size(); ++i) {
|
|
141
|
+
*indices_array_ptr++ = result[i].first[result[i].first.size() - 1];
|
|
142
|
+
*distances_array_ptr++ = result[i].second[result[i].second.size() - 1];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return std::make_pair(indices_array, distances_array);
|
|
146
|
+
},
|
|
147
|
+
"Find nearest neighbors for all points in a cloud!");
|
|
148
|
+
|
|
149
|
+
// Pickling support for the KDTree data structure
|
|
150
|
+
kdtree.def("__getstate__", [](const KDTree&) {
|
|
151
|
+
// If a user pickles KDTree itself, we end up redundantly storing
|
|
152
|
+
// the point cloud itself, because the KDTree is only usable with the
|
|
153
|
+
// cloud (scipy does exactly the same). We solve the problem by asking
|
|
154
|
+
// users to pickle Epoch instead, which is the much cleaner solution.
|
|
155
|
+
throw std::runtime_error{
|
|
156
|
+
"Please pickle Epoch instead of KDTree. Otherwise unpickled KDTree does "
|
|
157
|
+
"not know the point cloud."
|
|
158
|
+
};
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Segment point cloud into a supervoxels
|
|
162
|
+
m.def("segment_pc_in_supervoxels",
|
|
163
|
+
[](Epoch& epoch,
|
|
164
|
+
const KDTree& kdtree,
|
|
165
|
+
EigenNormalSetConstRef normals,
|
|
166
|
+
double resolution,
|
|
167
|
+
int k,
|
|
168
|
+
int minSVPvalue) {
|
|
169
|
+
std::vector<Supervoxel> supervoxels =
|
|
170
|
+
segment_pc(epoch, kdtree, normals, resolution, k, minSVPvalue);
|
|
171
|
+
|
|
172
|
+
py::list np_arrays_cloud;
|
|
173
|
+
py::list np_arrays_centroid;
|
|
174
|
+
py::list np_arrays_boundary_points;
|
|
175
|
+
py::list np_arrays_normals;
|
|
176
|
+
|
|
177
|
+
for (const auto& sv : supervoxels) {
|
|
178
|
+
// Convert Eigen::MatrixXd to a NumPy array
|
|
179
|
+
auto np_array_cloud = py::array_t<double>(
|
|
180
|
+
sv.cloud.rows() * sv.cloud.cols(), sv.cloud.data());
|
|
181
|
+
auto np_array_normals = py::array_t<double>(
|
|
182
|
+
sv.normals.rows() * sv.normals.cols(), sv.normals.data());
|
|
183
|
+
auto np_array_centroid =
|
|
184
|
+
py::array_t<double>(sv.centroid.size(), sv.centroid.data());
|
|
185
|
+
auto np_array_boundary_points = py::array_t<double>(
|
|
186
|
+
sv.boundary_points.rows() * sv.boundary_points.cols(),
|
|
187
|
+
sv.boundary_points.data());
|
|
188
|
+
|
|
189
|
+
// Reshape the arrays to their original shape
|
|
190
|
+
np_array_cloud.resize({ sv.cloud.rows(), sv.cloud.cols() });
|
|
191
|
+
np_array_normals.resize({ sv.normals.rows(), sv.normals.cols() });
|
|
192
|
+
np_array_centroid.resize({ sv.centroid.size() });
|
|
193
|
+
np_array_boundary_points.resize(
|
|
194
|
+
{ sv.boundary_points.rows(), sv.boundary_points.cols() });
|
|
195
|
+
|
|
196
|
+
np_arrays_cloud.append(np_array_cloud);
|
|
197
|
+
np_arrays_normals.append(np_array_normals);
|
|
198
|
+
np_arrays_centroid.append(np_array_centroid);
|
|
199
|
+
np_arrays_boundary_points.append(np_array_boundary_points);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return std::make_tuple(np_arrays_cloud,
|
|
203
|
+
np_arrays_normals,
|
|
204
|
+
np_arrays_centroid,
|
|
205
|
+
np_arrays_boundary_points);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Perform a transformation of a point cloud using Gauss-Newton method
|
|
209
|
+
m.def("fit_transform_GN",
|
|
210
|
+
[](EigenPointCloudConstRef cloud1,
|
|
211
|
+
EigenPointCloudConstRef cloud2,
|
|
212
|
+
EigenNormalSetConstRef normals) {
|
|
213
|
+
Eigen::Matrix4d transformation =
|
|
214
|
+
fit_transform_GN(cloud1, cloud2, normals);
|
|
215
|
+
return transformation;
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// The main distance computation function that is the main entry point of M3C2
|
|
219
|
+
m.def(
|
|
220
|
+
"compute_distances",
|
|
221
|
+
[](EigenPointCloudConstRef corepoints,
|
|
222
|
+
double scale,
|
|
223
|
+
const Epoch& epoch1,
|
|
224
|
+
const Epoch& epoch2,
|
|
225
|
+
EigenNormalSetConstRef directions,
|
|
226
|
+
double max_distance,
|
|
227
|
+
double registration_error,
|
|
228
|
+
const WorkingSetFinderCallback& workingsetfinder,
|
|
229
|
+
const DistanceUncertaintyCalculationCallback& distancecalculator) {
|
|
230
|
+
// Allocate memory for the return types
|
|
231
|
+
DistanceVector distances;
|
|
232
|
+
UncertaintyVector uncertainties;
|
|
233
|
+
|
|
234
|
+
{
|
|
235
|
+
// compute_distances may spawn multiple threads that may call Python
|
|
236
|
+
// functions (which requires them to acquire the GIL), so we need to
|
|
237
|
+
// first release the GIL on the main thread before calling
|
|
238
|
+
// compute_distances
|
|
239
|
+
py::gil_scoped_release release_gil;
|
|
240
|
+
compute_distances(corepoints,
|
|
241
|
+
scale,
|
|
242
|
+
epoch1,
|
|
243
|
+
epoch2,
|
|
244
|
+
directions,
|
|
245
|
+
max_distance,
|
|
246
|
+
registration_error,
|
|
247
|
+
distances,
|
|
248
|
+
uncertainties,
|
|
249
|
+
workingsetfinder,
|
|
250
|
+
distancecalculator);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return std::make_tuple(as_pyarray(std::move(distances)),
|
|
254
|
+
as_pyarray(std::move(uncertainties)));
|
|
255
|
+
},
|
|
256
|
+
"The main M3C2 distance calculation algorithm");
|
|
257
|
+
|
|
258
|
+
// Multiscale direction computation
|
|
259
|
+
m.def(
|
|
260
|
+
"compute_multiscale_directions",
|
|
261
|
+
[](const Epoch& epoch,
|
|
262
|
+
EigenPointCloudConstRef corepoints,
|
|
263
|
+
const std::vector<double>& normal_radii,
|
|
264
|
+
EigenNormalSetConstRef orientation) {
|
|
265
|
+
EigenNormalSet result(corepoints.rows(), 3);
|
|
266
|
+
std::vector<double> used_radii;
|
|
267
|
+
|
|
268
|
+
compute_multiscale_directions(
|
|
269
|
+
epoch, corepoints, normal_radii, orientation, result, used_radii);
|
|
270
|
+
|
|
271
|
+
return std::make_tuple(std::move(result),
|
|
272
|
+
as_pyarray(std::move(used_radii)));
|
|
273
|
+
},
|
|
274
|
+
"Compute M3C2 multiscale directions");
|
|
275
|
+
|
|
276
|
+
// Corresponence distances computation
|
|
277
|
+
m.def("compute_correspondence_distances",
|
|
278
|
+
&compute_correspondence_distances,
|
|
279
|
+
"Compute correspondence distances");
|
|
280
|
+
|
|
281
|
+
// Callback parameter structs
|
|
282
|
+
py::class_<WorkingSetFinderParameters> ws_params(
|
|
283
|
+
m, "WorkingSetFinderParameters");
|
|
284
|
+
ws_params.def_property_readonly(
|
|
285
|
+
"epoch", [](const WorkingSetFinderParameters& self) { return self.epoch; });
|
|
286
|
+
ws_params.def_property_readonly(
|
|
287
|
+
"radius",
|
|
288
|
+
[](const WorkingSetFinderParameters& self) { return self.radius; });
|
|
289
|
+
ws_params.def_property_readonly(
|
|
290
|
+
"corepoint",
|
|
291
|
+
[](const WorkingSetFinderParameters& self) { return self.corepoint; });
|
|
292
|
+
ws_params.def_property_readonly(
|
|
293
|
+
"cylinder_axis",
|
|
294
|
+
[](const WorkingSetFinderParameters& self) { return self.cylinder_axis; });
|
|
295
|
+
ws_params.def_property_readonly(
|
|
296
|
+
"max_distance",
|
|
297
|
+
[](const WorkingSetFinderParameters& self) { return self.max_distance; });
|
|
298
|
+
|
|
299
|
+
py::class_<DistanceUncertaintyCalculationParameters> d_params(
|
|
300
|
+
m, "DistanceUncertaintyCalculationParameters");
|
|
301
|
+
d_params.def_property_readonly(
|
|
302
|
+
"workingset1", [](const DistanceUncertaintyCalculationParameters& self) {
|
|
303
|
+
return self.workingset1;
|
|
304
|
+
});
|
|
305
|
+
d_params.def_property_readonly(
|
|
306
|
+
"workingset2", [](const DistanceUncertaintyCalculationParameters& self) {
|
|
307
|
+
return self.workingset2;
|
|
308
|
+
});
|
|
309
|
+
d_params.def_property_readonly(
|
|
310
|
+
"corepoint", [](const DistanceUncertaintyCalculationParameters& self) {
|
|
311
|
+
return self.corepoint;
|
|
312
|
+
});
|
|
313
|
+
d_params.def_property_readonly(
|
|
314
|
+
"normal", [](const DistanceUncertaintyCalculationParameters& self) {
|
|
315
|
+
return self.normal;
|
|
316
|
+
});
|
|
317
|
+
d_params.def_property_readonly(
|
|
318
|
+
"registration_error",
|
|
319
|
+
[](const DistanceUncertaintyCalculationParameters& self) {
|
|
320
|
+
return self.registration_error;
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// The ObjectByChange class is used as the return type for spatiotemporal
|
|
324
|
+
// segmentations
|
|
325
|
+
py::class_<ObjectByChange> obc(m, "ObjectByChange");
|
|
326
|
+
obc.def_property_readonly(
|
|
327
|
+
"indices_distances",
|
|
328
|
+
[](const ObjectByChange& self) { return self.indices_distances; });
|
|
329
|
+
obc.def_property_readonly(
|
|
330
|
+
"start_epoch", [](const ObjectByChange& self) { return self.start_epoch; });
|
|
331
|
+
obc.def_property_readonly(
|
|
332
|
+
"end_epoch", [](const ObjectByChange& self) { return self.end_epoch; });
|
|
333
|
+
obc.def_property_readonly(
|
|
334
|
+
"threshold", [](const ObjectByChange& self) { return self.threshold; });
|
|
335
|
+
obc.def(py::pickle(
|
|
336
|
+
[](const ObjectByChange& self) {
|
|
337
|
+
// Serialize into in-memory stream
|
|
338
|
+
std::stringstream buf;
|
|
339
|
+
|
|
340
|
+
// Write indices
|
|
341
|
+
std::size_t size = self.indices_distances.size();
|
|
342
|
+
buf.write(reinterpret_cast<const char*>(&size), sizeof(std::size_t));
|
|
343
|
+
for (auto p : self.indices_distances)
|
|
344
|
+
buf.write(reinterpret_cast<const char*>(&p),
|
|
345
|
+
sizeof(std::pair<IndexType, double>));
|
|
346
|
+
|
|
347
|
+
// Write other data
|
|
348
|
+
buf.write(reinterpret_cast<const char*>(&self.start_epoch),
|
|
349
|
+
sizeof(IndexType));
|
|
350
|
+
buf.write(reinterpret_cast<const char*>(&self.end_epoch),
|
|
351
|
+
sizeof(IndexType));
|
|
352
|
+
buf.write(reinterpret_cast<const char*>(&self.threshold), sizeof(double));
|
|
353
|
+
return py::bytes(buf.str());
|
|
354
|
+
},
|
|
355
|
+
[](const py::bytes& data) {
|
|
356
|
+
std::stringstream buf(data.cast<std::string>());
|
|
357
|
+
ObjectByChange obj;
|
|
358
|
+
|
|
359
|
+
std::size_t size;
|
|
360
|
+
buf.read(reinterpret_cast<char*>(&size), sizeof(std::size_t));
|
|
361
|
+
std::pair<IndexType, double> buffer;
|
|
362
|
+
for (std::size_t i = 0; i < size; ++i) {
|
|
363
|
+
buf.read(reinterpret_cast<char*>(&buffer),
|
|
364
|
+
sizeof(std::pair<IndexType, double>));
|
|
365
|
+
obj.indices_distances.insert(buffer);
|
|
366
|
+
}
|
|
367
|
+
buf.read(reinterpret_cast<char*>(&obj.start_epoch), sizeof(IndexType));
|
|
368
|
+
buf.read(reinterpret_cast<char*>(&obj.end_epoch), sizeof(IndexType));
|
|
369
|
+
buf.read(reinterpret_cast<char*>(&obj.threshold), sizeof(double));
|
|
370
|
+
return obj;
|
|
371
|
+
}));
|
|
372
|
+
|
|
373
|
+
py::class_<RegionGrowingSeed> rgs(m, "RegionGrowingSeed");
|
|
374
|
+
rgs.def(py::init<IndexType, IndexType, IndexType>(),
|
|
375
|
+
py::arg("index"),
|
|
376
|
+
py::arg("start_epoch"),
|
|
377
|
+
py::arg("end_epoch"));
|
|
378
|
+
rgs.def_property_readonly(
|
|
379
|
+
"index", [](const RegionGrowingSeed& self) { return self.index; });
|
|
380
|
+
rgs.def_property_readonly("start_epoch", [](const RegionGrowingSeed& self) {
|
|
381
|
+
return self.start_epoch;
|
|
382
|
+
});
|
|
383
|
+
rgs.def_property_readonly(
|
|
384
|
+
"end_epoch", [](const RegionGrowingSeed& self) { return self.end_epoch; });
|
|
385
|
+
rgs.def(py::pickle(
|
|
386
|
+
[](const RegionGrowingSeed& self) {
|
|
387
|
+
// Serialize into in-memory stream
|
|
388
|
+
std::stringstream buf;
|
|
389
|
+
buf.write(reinterpret_cast<const char*>(&self.index), sizeof(IndexType));
|
|
390
|
+
buf.write(reinterpret_cast<const char*>(&self.start_epoch),
|
|
391
|
+
sizeof(IndexType));
|
|
392
|
+
buf.write(reinterpret_cast<const char*>(&self.end_epoch),
|
|
393
|
+
sizeof(IndexType));
|
|
394
|
+
return py::bytes(buf.str());
|
|
395
|
+
},
|
|
396
|
+
[](const py::bytes& data) {
|
|
397
|
+
std::stringstream buf(data.cast<std::string>());
|
|
398
|
+
IndexType index, start_epoch, end_epoch;
|
|
399
|
+
buf.read(reinterpret_cast<char*>(&index), sizeof(IndexType));
|
|
400
|
+
buf.read(reinterpret_cast<char*>(&start_epoch), sizeof(IndexType));
|
|
401
|
+
buf.read(reinterpret_cast<char*>(&end_epoch), sizeof(IndexType));
|
|
402
|
+
return RegionGrowingSeed{ index, start_epoch, end_epoch };
|
|
403
|
+
}));
|
|
404
|
+
|
|
405
|
+
py::class_<RegionGrowingAlgorithmData> rgwd(m, "RegionGrowingAlgorithmData");
|
|
406
|
+
rgwd.def(py::init<EigenSpatiotemporalArrayConstRef,
|
|
407
|
+
const Epoch&,
|
|
408
|
+
double,
|
|
409
|
+
RegionGrowingSeed,
|
|
410
|
+
std::vector<double>,
|
|
411
|
+
std::size_t,
|
|
412
|
+
std::size_t>(),
|
|
413
|
+
py::arg("data"),
|
|
414
|
+
py::arg("epoch"),
|
|
415
|
+
py::arg("radius"),
|
|
416
|
+
py::arg("seed"),
|
|
417
|
+
py::arg("thresholds"),
|
|
418
|
+
py::arg("min_segments"),
|
|
419
|
+
py::arg("max_segments"));
|
|
420
|
+
|
|
421
|
+
py::class_<TimeseriesDistanceFunctionData> tdfd(
|
|
422
|
+
m, "TimeseriesDistanceFunctionData");
|
|
423
|
+
tdfd.def(py::init<EigenTimeSeriesConstRef, EigenTimeSeriesConstRef>(),
|
|
424
|
+
py::arg("ts1"),
|
|
425
|
+
py::arg("ts2"));
|
|
426
|
+
tdfd.def_property_readonly(
|
|
427
|
+
"ts1", [](const TimeseriesDistanceFunctionData& self) { return self.ts1; });
|
|
428
|
+
tdfd.def_property_readonly(
|
|
429
|
+
"ts2", [](const TimeseriesDistanceFunctionData& self) { return self.ts2; });
|
|
430
|
+
tdfd.def_property_readonly(
|
|
431
|
+
"norm1",
|
|
432
|
+
[](const TimeseriesDistanceFunctionData& self) { return self.norm1; });
|
|
433
|
+
tdfd.def_property_readonly(
|
|
434
|
+
"norm2",
|
|
435
|
+
[](const TimeseriesDistanceFunctionData& self) { return self.norm2; });
|
|
436
|
+
|
|
437
|
+
py::class_<ChangePointDetectionData> cpdd(m, "ChangePointDetectionData");
|
|
438
|
+
cpdd.def(
|
|
439
|
+
py::
|
|
440
|
+
init<EigenTimeSeriesConstRef, IndexType, IndexType, IndexType, double>(),
|
|
441
|
+
py::arg("ts"),
|
|
442
|
+
py::arg("window_size"),
|
|
443
|
+
py::arg("min_size"),
|
|
444
|
+
py::arg("jump"),
|
|
445
|
+
py::arg("penalty"));
|
|
446
|
+
|
|
447
|
+
m.def("transform_pointcloud_inplace",
|
|
448
|
+
[](EigenPointCloudRef cloud,
|
|
449
|
+
const py::array_t<double>& t,
|
|
450
|
+
EigenPointCloudConstRef rp,
|
|
451
|
+
EigenNormalSetRef normals) {
|
|
452
|
+
Transformation trafo;
|
|
453
|
+
|
|
454
|
+
auto r = t.unchecked<2>();
|
|
455
|
+
for (IndexType i = 0; i < 4; ++i)
|
|
456
|
+
for (IndexType j = 0; j < 4; ++j)
|
|
457
|
+
trafo(i, j) = r(i, j);
|
|
458
|
+
|
|
459
|
+
transform_pointcloud_inplace(cloud, trafo, rp, normals);
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
// The main algorithms for the spatiotemporal segmentations
|
|
463
|
+
m.def("region_growing",
|
|
464
|
+
[](const RegionGrowingAlgorithmData& data,
|
|
465
|
+
const TimeseriesDistanceFunction& distance_function) {
|
|
466
|
+
// The region_growing function may call Python callback functions
|
|
467
|
+
py::gil_scoped_release release_gil;
|
|
468
|
+
return region_growing(data, distance_function);
|
|
469
|
+
});
|
|
470
|
+
m.def("change_point_detection", &change_point_detection);
|
|
471
|
+
|
|
472
|
+
// Callback implementations
|
|
473
|
+
m.def("radius_workingset_finder", &radius_workingset_finder);
|
|
474
|
+
m.def("cylinder_workingset_finder", &cylinder_workingset_finder);
|
|
475
|
+
m.def("mean_stddev_distance", &mean_stddev_distance);
|
|
476
|
+
m.def("median_iqr_distance", &median_iqr_distance);
|
|
477
|
+
m.def("dtw_distance", &dtw_distance);
|
|
478
|
+
m.def("normalized_dtw_distance", &normalized_dtw_distance);
|
|
479
|
+
|
|
480
|
+
// Expose OpenMP threading control
|
|
481
|
+
#ifdef PY4DGEO_WITH_OPENMP
|
|
482
|
+
m.def("omp_set_num_threads", &omp_set_num_threads);
|
|
483
|
+
m.def("omp_get_max_threads", &omp_get_max_threads);
|
|
484
|
+
#endif
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
} // namespace py4dgeo
|