Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion docs/stubfiles/cccorelib.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,20 @@ class ICPRegistrationTools(RegistrationTools):
def __init__(self, *args, **kwargs) -> None: ...
def Register(self, *args, **kwargs) -> Any: ...

class Jacobi:
@staticmethod
def ComputeEigenValuesAndVectors(matrix: SquareMatrixd, absoluteValues: bool = ..., maxIterationCount: int = ...) -> Tuple[SquareMatrixd, list[float]]: ...
@staticmethod
def SortEigenValuesAndVectors(eigenVectors: SquareMatrixd, eigenValues: list[float]) -> Tuple[SquareMatrixd, list[float]]: ...
@staticmethod
def GetEigenVector(eigenVectors: SquareMatrixd, index: int) -> list[float]: ...
@staticmethod
def GetMaxEigenValueAndVector(eigenVectors: SquareMatrixd, eigenValues: list[float]) -> Tuple[float, list[float]]: ...
@staticmethod
def GetMinEigenValueAndVector(eigenVectors: SquareMatrixd, eigenValues: list[float]) -> Tuple[float, list[float]]: ...
@staticmethod
def TestEigenVecAndValues(matrix: SquareMatrixd, eigenVectors: SquareMatrixd, eigenValues: list[float]) -> bool: ...

class KDTree:
IndicesVector: Any = ...
def __init__(self) -> None: ...
Expand Down Expand Up @@ -491,6 +505,24 @@ class ManualSegmentationTools:
def segmentMeshWithAAPlane(self, *args, **kwargs) -> Any: ...
def segmentReferenceCloud(self, *args, **kwargs) -> Any: ...

class MeshSamplingTools:
EdgeConnectivityStats: Any = ...
VertexFlags: Any = ...
VERTEX_NORMAL: Any = ...
VERTEX_BORDER: Any = ...
VERTEX_NON_MANIFOLD: Any = ...
def __init__(self, *args, **kwargs) -> None: ...
@staticmethod
def computeMeshArea(mesh: GenericMesh) -> float: ...
@staticmethod
def computeMeshVolume(mesh: GenericMesh) -> float: ...
@staticmethod
def computeMeshEdgesConnectivity(mesh: GenericIndexedMesh) -> Any: ...
@staticmethod
def flagMeshVerticesByType(mesh: GenericIndexedMesh, flags: ScalarField) -> Any: ...
@staticmethod
def samplePointsOnMesh(mesh: GenericMesh, *args, **kwargs) -> Tuple[PointCloud, list[int]]: ...

class Neighbourhood:
GeomElement: Any = ...
FLAG_DEPRECATED: Any = ...
Expand Down Expand Up @@ -680,17 +712,27 @@ class ScalarField(CCShareable):
def NaN(self, *args, **kwargs) -> Any: ...
def ValidValue(self, *args, **kwargs) -> Any: ...
def asArray(self) -> numpy.ndarray[numpy.float32]: ...
def clear(self) -> None: ...
def computeMeanAndVariance(self, mean: float, variance: float = ...) -> None: ...
def computeMinAndMax(self) -> None: ...
def fill(self, fillValue: float = ...) -> None: ...
def countValidValues(self) -> int: ...
def fill(self, fillValue: float = ..., autoResetOffset: bool = ...) -> None: ...
def flagValueAsInvalid(self, index: int) -> None: ...
def getLocalValue(self, index: int) -> float: ...
def getLocalValues(self) -> numpy.ndarray[numpy.float32]: ...
def getMax(self) -> float: ...
def getMin(self) -> float: ...
def getName(self) -> str: ...
def getOffset(self) -> float: ...
def invert(self) -> None: ...
def reserveSafe(self, count: int) -> bool: ...
def resetOffset(self) -> None: ...
def resizeSafe(self, count: int, initNewElements: bool = ..., valueForNewElements: float = ...) -> bool: ...
def setLocalValue(self, index: int, value: float) -> None: ...
def setName(self, arg0: str) -> None: ...
def setOffset(self, offset: float) -> None: ...
def size(self) -> int: ...
def swap(self, i1: int, i2: int) -> None: ...
def __getitem__(self, arg0: int) -> float: ...
def __setitem__(self, arg0: int, arg1: float) -> None: ...

Expand Down
6 changes: 3 additions & 3 deletions tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ def tqdm(iter, *args, **kwargs):
# List of folder names to be ignoreed when formatting
DISALLOW_LIST = ['_skbuild']

VENV_PYTHON_EXEC = "./venv/bin/python"


def filter_paths(paths: Iterable[Path]) -> List[Path]:
filtered_paths = []
Expand Down Expand Up @@ -63,8 +65,6 @@ def cmake_format(c):
def format(c):
...

VENV_PYTHON_EXEC = "./venv/bin/python"

@task
def create_venv(c):
if not Path("venv").exists():
Expand All @@ -74,7 +74,7 @@ def create_venv(c):
print("venv already exists")

@task
def install_ccorelib(c):
def install_cccorelib(c):
c.run(f"{VENV_PYTHON_EXEC} -m pip install --verbose wrapper/cccorelib")

@task
Expand Down
2 changes: 2 additions & 0 deletions wrapper/cccorelib/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ set(cccorelib_sources
${CMAKE_CURRENT_LIST_DIR}/GenericProgressCallback.cpp
${CMAKE_CURRENT_LIST_DIR}/GenericTriangle.cpp
${CMAKE_CURRENT_LIST_DIR}/GeometricalAnalysisTools.cpp
${CMAKE_CURRENT_LIST_DIR}/Jacobi.cpp
${CMAKE_CURRENT_LIST_DIR}/KdTree.cpp
${CMAKE_CURRENT_LIST_DIR}/LocalModel.cpp
${CMAKE_CURRENT_LIST_DIR}/ManualSegmentationTools.cpp
${CMAKE_CURRENT_LIST_DIR}/MeshSamplingTools.cpp
${CMAKE_CURRENT_LIST_DIR}/Neighbourhood.cpp
${CMAKE_CURRENT_LIST_DIR}/NormalDistribution.cpp
${CMAKE_CURRENT_LIST_DIR}/PointCloud.cpp
Expand Down
174 changes: 174 additions & 0 deletions wrapper/cccorelib/src/Jacobi.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// ##########################################################################
// # #
// # CLOUDCOMPARE PLUGIN: PythonRuntime #
// # #
// # This program is free software; you can redistribute it and/or modify #
// # it under the terms of the GNU General Public License as published by #
// # the Free Software Foundation; version 2 of the License. #
// # #
// # This program is distributed in the hope that it will be useful, #
// # but WITHOUT ANY WARRANTY; without even the implied warranty of #
// # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
// # GNU General Public License for more details. #
// # #
// # COPYRIGHT: Thomas Montaigu #
// # #
// ##########################################################################

#include <pybind11/pybind11.h>
#include <pybind11/stl.h>

#include <Jacobi.h>
#include <SquareMatrix.h>

namespace py = pybind11;
using namespace pybind11::literals;

void define_Jacobi(py::module &cccorelib)
{
// Only the double instantiation of Jacobi makes sense
// because it's the only one used in CC and python's float are double
using Jacobi_d = CCCoreLib::Jacobi<double>;
using SquareMatrixd = CCCoreLib::SquareMatrixd;

py::class_<Jacobi_d> PyJacobi(cccorelib, "Jacobi", R"doc(
Jacobi eigenvalue / eigenvector decomposition for symmetric SquareMatrixd.
)doc");

PyJacobi
.def_static(
"ComputeEigenValuesAndVectors",
[](SquareMatrixd &matrix, bool absoluteValues, unsigned maxIterationCount)
{
SquareMatrixd eigenVectors;
std::vector<double> eigenValues;
if (!Jacobi_d::ComputeEigenValuesAndVectors(
matrix, eigenVectors, eigenValues, absoluteValues, maxIterationCount))
{
throw std::runtime_error("Jacobi.ComputeEigenValuesAndVectors failed (invalid matrix, "
"allocation failure, or no convergence within "
"maxIterationCount sweeps).");
}
return py::make_tuple(eigenVectors, eigenValues);
},
"matrix"_a,
"absoluteValues"_a = true,
"maxIterationCount"_a = 50,
R"doc(
Compute eigenvalues and eigenvectors of a symmetric SquareMatrixd.

Parameters
----------
matrix : SquareMatrixd
Symmetric square matrix (modified in place).
absoluteValues : bool, optional
If True (default), the eigenvalues are returned as absolute values.
maxIterationCount : int, optional
Maximum number of Jacobi sweeps before giving up. Default 50.

Returns
-------
tuple (eigenVectors, eigenValues)
``eigenVectors`` is a SquareMatrixd whose columns are the
eigenvectors; ``eigenValues`` is a list of floats in the same order
as the columns.

Raises
------
RuntimeError
If the matrix is invalid, allocation fails, or the algorithm does
not converge within ``maxIterationCount`` sweeps.
)doc")

.def_static(
"SortEigenValuesAndVectors",
// Take by value so the original Python objects are untouched; we
// return freshly sorted copies. This is a more natural Python
// contract than the C++ in-place mutation.
[](SquareMatrixd eigenVectors, std::vector<double> eigenValues)
{
if (!Jacobi_d::SortEigenValuesAndVectors(eigenVectors, eigenValues))
{
throw std::runtime_error(
"Jacobi.SortEigenValuesAndVectors failed (invalid matrix, "
"size < 2, or size mismatch between eigenVectors and eigenValues).");
}
return py::make_tuple(eigenVectors, eigenValues);
},
"eigenVectors"_a,
"eigenValues"_a,
R"doc(
Return a copy of the eigen pair sorted by decreasing eigenvalue.

Unlike the C++ version (which sorts in place), the Python binding
returns new ``(sortedVectors, sortedValues)`` objects so mutation
semantics are explicit.
)doc")

.def_static(
"GetEigenVector",
[](const SquareMatrixd &eigenVectors, unsigned index)
{
std::vector<double> out(eigenVectors.size());
if (!Jacobi_d::GetEigenVector(eigenVectors, index, out.data()))
{
throw std::runtime_error("Jacobi.GetEigenVector: index out of bounds.");
}
return out;
},
"eigenVectors"_a,
"index"_a,
"Return the ``index``-th eigenvector (column of ``eigenVectors``) as a list of floats.")

.def_static(
"GetMaxEigenValueAndVector",
[](const SquareMatrixd &eigenVectors, const std::vector<double> &eigenValues)
{
double maxValue = 0.0;
std::vector<double> maxVector(eigenVectors.size());
if (!Jacobi_d::GetMaxEigenValueAndVector(
eigenVectors, eigenValues, maxValue, maxVector.data()))
{
throw std::runtime_error("Jacobi.GetMaxEigenValueAndVector failed (invalid matrix, "
"size < 2, or size mismatch).");
}
return py::make_tuple(maxValue, maxVector);
},
"eigenVectors"_a,
"eigenValues"_a,
"Return ``(maxEigenvalue, maxEigenvector_as_list)``.")

.def_static(
"GetMinEigenValueAndVector",
[](const SquareMatrixd &eigenVectors, const std::vector<double> &eigenValues)
{
double minValue = 0.0;
std::vector<double> minVector(eigenVectors.size());
if (!Jacobi_d::GetMinEigenValueAndVector(
eigenVectors, eigenValues, minValue, minVector.data()))
{
throw std::runtime_error("Jacobi.GetMinEigenValueAndVector failed (invalid matrix, "
"size < 2, or size mismatch).");
}
return py::make_tuple(minValue, minVector);
},
"eigenVectors"_a,
"eigenValues"_a,
"Return ``(minEigenvalue, minEigenvector_as_list)``.")

.def_static("TestEigenVecAndValues",
&Jacobi_d::TestEigenVecAndValues,
"matrix"_a,
"eigenVectors"_a,
"eigenValues"_a,
R"doc(
Check consistency of a ``(matrix, eigenVectors, eigenValues)`` triple.

Returns True when ``matrix @ v ≈ lambda * v`` for each column of
``eigenVectors`` (within 1e-5), False otherwise.

.. note::
The underlying C++ implementation assumes a 3x3 matrix — use this
only for 3x3 cases.
)doc");
}
Loading
Loading