Creating a new conda environment or a virtual environment with Python 3.10+.
conda create -y -n PynamicMesh -c conda-forge python=3.11
conda activate PynamicMeshClone the repo and install dependencies:
git clone https://github.com/MMV-Lab/PynamicMesh
cd PynamicMesh
# Instalation with only CPU support
pip install .
# Instalation with GPU support depend in your drivers options
pip install ".[gpu-12x]"
pip install ".[gpu-13x]"
pip install ".[rocm-7-0]"
# Instalation with editable mood for developers
pip install -e .All the following examples can be replicated using the meshes and the generated example files provided here, and all the code usage syntax is summarized in the provided code.
The provided tools were developed independently into pure computations and visualization/graphical tools in order to keep the flexibility of running the computations on a pure non-graphic node or high-performance computing cluster.
Given a family of meshes
PynamicMesh offers a full general range of pipelines based on Topology, Differential Geometry, and Physics in order to model the complex dynamics encoded in the transformation, allowing the extraction of features that help to characterize and understand the dynamical process.
For a detailed and applied understanding of meshes as Manifolds and triangulations, the following Jupyter Notebook might interest you.
Global Geometry
This Basic analysis offer a time tracking of the global geometry features of the mesh.
Generating a csv (features_computed.csv) with the metric values and the plot of each one (mesh_evolution_summary.png) within the folder ./PynamicMesh/Results/scene1/Basic_Geometry/.
from PynamicMesh.core.pipelines import run_pipeline
run_pipeline(**args)In order to track the Global Geometry, run:
from PynamicMesh.core.pipelines import run_pipeline
run_pipeline(
path_str='base/path',
compute_basicGeo = True,
metrics = 'all',
plot_basicGeo = True
)Or you can set your parameters on the yaml file, and within the PynamicMesh enviroment run on the comand line:
run_pynamic --config /path/to/the/config.yaml
Note:
The volume based metrics (volume, sphericity, convexity, surface_to_volume) are always computed with the divergence theorem on the original faces of the mesh, and the column is_watertight reports whether the mesh is closed and manifold at that time step. When it is not, the values are the approximation for an almost closed surface and the frame is marked with a red cross on the corresponding graphs; use the topology metrics (n_boundary_edges, n_components) to see why. The mesh is never modified before measuring it: welding coincident vertices (as some libraries do by default) turns touching parts of the shape (legs, belly) into non-manifold edges and silently breaks the volume computation of a perfectly closed mesh.
Global Geometry Parameters
Path to the root folder that contains the scenes:
path_str (str) Flag to indicate the execution:
compute_basicGeo (bool)Flag to indicate the plot of the metrics:
plot_basicGeo (bool)Desired metrics to track:
metrics (str) | (list)Available metrics
Compute and report all the available metrics :
metrics (str) : 'all'Compute just the selected set of metrics :
metrics (list) : ['n_vertices', 'n_faces', 'area', 'volume', 'sphericity', 'convexity', 'center_mass', 'gaussian_curvature', 'mean_curvature', 'topology', 'bounding_box', 'shape']Some metrics produce several columns in the csv (e.g. center_mass -> cm_x, cm_y, cm_z); an unknown metric name raises an error instead of being silently ignored.
Mesh Detail:
n_vertices and n_faces captures the structural resolution of the mesh (the total number of points and connecting triangles), constant values mean the object is changing shape or moving without altering its basic blueprint.
Changing values mean the model is actively gaining or losing detail (e.g., tearing, merging, or adaptive rewriting).
Surface Area:
area captures the total amount of outside covering for the mesh, tracks stretching and compression. If the surface area spikes while the overall size remains constant, it indicates that the object is wrinkling, crumpling, or becoming highly textured.
Volume:
volume captures the total relative amount of physical space enclosed inside the object, tracks inflation and deflation. A steady volume means the object is maintaining its physical mass/size while it moves or bends. It comes with the flag is_watertight: True means the mesh is closed and manifold and the volume is exact; False means the value is the approximation for an almost closed surface (open boundary or non-manifold edges, see Topology).
Sphericity:
sphericity captures the roundness score from 0 to 1, evaluating how closely the object resembles a perfect ball (1 being a perfect sphere). A rising score means the object is compacting or pulling itself together into a ball shape.
A falling score means it is stretching out, flattening, or growing irregular limbs.
Convexity:
convexity captures a "bulginess" score measuring how many hollows, indents, or valleys the object has. A high score indicates a smooth, rounded object. A decreasing score means the object is actively folding in on itself, developing deep cavities, or sprouting appendages.
Surface Curvature:
gaussian_curvature computes the discrete Gaussian curvature at every vertex (angle deficit
mean_gaussian_curvature the area weighted average texture profile of the surface, distinguishing between dome-like features (
mean_abs_gaussian_curvature and total_abs_gaussian_curvature (gaussian_curvature_std measures how uneven the texture is over the surface.
mean_curvature computes the discrete mean curvature of every vertex (cotangent formula, mean_mean_curvature, mean_abs_mean_curvature and the willmore_energy
Topology:
topology reports the euler_number genus (number of handles; only defined for closed meshes, NaN otherwise), the n_boundary_edges (0 for a closed surface; holes and tears appear here) and the n_components (disconnected pieces). Constant values mean the shape deforms without changing its structure; a change flags a topological event (tearing, merging, splitting) or a mesh defect, and explains a False in is_watertight.
Extent and Shape:
bounding_box reports the extents bbox_dx, bbox_dy, bbox_dz and the bbox_diagonal of the axis aligned bounding box: the overall size of the object in each direction (depends on the orientation of the frames).
shape reports orientation independent shape descriptors from the area weighted principal axes of the surface: elongation (ratio between the first and second principal extents, 1 for an isotropic object, growing as it stretches along one direction), flatness (ratio between the second and third principal extents, growing as the object becomes plate-like), surface_to_volume (skin per unit of enclosed volume, the inverse compactness) and radius_of_gyration (spread of the surface around its centroid).
Relative Movement Speed:
center_mass captures the straight-line distance traveled by the object's center of gravity from one time frame to the next. Tracks overall speed. A flat line near zero means the object is stationary (even if it is spinning or shaking in place).
Sudden spikes indicate a sudden leap or fast global movement across space. The center of gravity is the volume based center of mass for closed meshes and the area weighted centroid of the surface otherwise.
Functional Map
General Overview
The functional map
In order to understand the dynamics of the deformation we can compute this matrix and vector in every time step
At the end we can use this representations to have a lot of features, those are defined and explained on the correspondig Functional Map Implementation Usage and Analysis section.
Mathematical Construction Details
Given two consecutive time step meshes
The goal is to find a representation of the unknown bijective transformation function
We can use a scalar function defined over each mesh
In our case we use as descriptors the point signatures built from the spectrum of the Laplace-Beltrami operator: for a spectral filter n_descr scales:
WKS (wave kernel, wave propagation function):
HKS (heat kernel, heat diffusion function):
MKS (Matérn kernel):
Where:
These kernels respect the surface geometry of the shape. Mathematically, they generalize the Laplace-Beltrami operator's spectral properties via the relationship with its eigenvalues. Any weighted sum of these families can be used as descriptor (see Functional Map Parameters), together with two extrinsic families that are aware of the symmetries of the shape (XYZ, NRM; see Symmetry-Aware Mapping).
The composition
As the linear function spaces
If we apply the Finite Element Method (FEM) to the Laplace-Beltrami equation of a function
Means that we want to compute the gradient of a function defined on a triangle, but locally the function varies linearly within each triangle
We can contruct the Connectivity matrix or the Cotan-Laplace operator
The "connectivity" is encoded in the adjacency of the mesh. The Laplacian matrix
$$L_{ij} = \left{ \begin{array}{cl} -w_{ij} & : v_i\to v_j \text{conected}\ 0 & : \text{ no conexion} \ \end{array} \right.$$
For the diagona the sum of weights of all edges connected to
$$ L_{ii} = \sum w_ii $$
This matrix
Then for every vertex
$$W_{ij} = \left{ \begin{array}{cl} A_i & : i=j\ 0 & \text{ other case} \ \end{array} \right.$$
This matrix essentially encode the surface area contribution of each vertex. Because a mesh is made of triangles, the "area" of a vertex is defined by the triangles that share it.
Then we can solve the generalized eigenvalue decomposition for a matrix
Only for the first
Once solved, each column
We can obtain this matrix for the
In theory this basis allows to express our fucntional as a linear combination:
$$\mathcal{F}{\varphi_n}(f) = \sum_k\sum_j a_jc{jk}\phi_k^{M_{t_{i}}}$$
This provide a matrix representation
We can express this coeficents using a inner product to project the tranformation represented on the domain base into the codomain base:
$$\displaystyle c_{jk}= \left\langle \mathcal{F}{\varphi_n}(\phi_k^{M{t_{i}}}) , \phi_j ^{M_{t_{i-1}}} \right\rangle$$
But now we have a matrix representation
We can use our descriptors in order to get a clue:
Each descriptor function
$$ A_m = \Phi_1^{T} W_1 \Psi_m^{t_{i-1}} \hspace{6mm} B_m = \Phi_2^{T} W_2 \Psi_m^{t_{i}} $$
When several descriptor families are combined, every family 'WKS+HKS' means equal shares and '0.7*WKS+0.3*HKS' means
The functional map matrix $\mathcal{F}{\varphi_n} = C{t_{i-1} \to t_{i}} \in \mathbb{M}_{k_2 \times k_1}(\mathbb{R})$ that we seek now is given for the one that minimizes the following objective function:
$$ \min_{C} E(C) = \underbrace{\lambda_{desc}\sum_{j} w_j \sum_{m \in j} | C A_m - B_m |^2}{E{desc}} + \underbrace{\lambda_{reg} | C \Lambda_1 - \Lambda_2 C |^2}{E{reg}} + \underbrace{\lambda_{comm} \sum_{m} | C D^{1}m - D^{2}m C |^2}{E{comm}} + \underbrace{\lambda_{orient} \sum_{m} | C G^{1}m - G^{2}m C |^2}{E{orient}} $$
Were:
fit_params: w_descr, w_lap, w_dcomm, w_orient).
symmetry_mode includes 'orientation').
Landmarks. A known correspondence
This optimal matrix
We need to take the basis representation of a point
And then transform it to the spectral domain of
Take the vertex
Functional Map Implementation Usage and Analysis
The computations are executed and managed through the syntax:
from PynamicMesh.core.pipelines import run_pipeline
run_pipeline(**args)In order to compute the Functional Map transformations, run:
from PynamicMesh.core.pipelines import run_pipeline
run_pipeline(
path_str='base/path',
matrix_tranformation=True,
diagonal_analysis=True,
isometric_analysis=True,
k_eigenfunctions=(10,10),
k_eigenvalues=100,
descriptor='0.6*WKS + 0.4*MKS',
landmarks='auto',
fm_params={'symmetry_mode': 'landmarks+orientation+extrinsic'},
compute_physic_fields=True,
)Or you can set your parameters on the yaml file, and within the PynamicMesh enviroment run on the comand line:
run_pynamic --config /path/to/the/config.yamlFunctional Map Parameters
Path to the root folder that contains the scenes:
path_str (str) Flag to indicate the model execution:
matrix_tranformation (bool)Flag to indicate the isometry analysis execution within the loop:
isometric_analysis (bool)Flag to indicate the diagonal analysis execution within the loop:
diagonal_analysis (bool)Flag to indicate the computation and storage of the physical fields (once); if it is false, the visualizer will compute them during execution time every time.
compute_physic_fields (bool)Descriptor families used on the pipeline and the share of the descriptor energy assigned to each one: WKS (wave propagation kernel), HKS (heat diffusion kernel), MKS (Matérn kernel), XYZ (aligned coordinates, symmetry-aware) and NRM (surface normals, symmetry-aware). Families are combined with +; an optional weight multiplies each family and the weights are renormalized to sum to one (families without weight share the remaining energy). Lists and dictionaries are also accepted.
descriptor (str|list|dict): 'WKS' | 'HKS' | 'MKS' | 'WKS+HKS+MKS' | '0.7*WKS + 0.3*MKS' | '0.5*WKS + 0.3*MKS + 0.2*XYZ' | {'WKS': 0.7, 'HKS': 0.3}Size of the functional map
k_eigenfunctions (int|tuple) : k | (k1,k2)Number of low frecuence eigenvalues
k_eigenvalues (int) Vertex indices indicators for symmetry restriction (see Landmark Options):
landmarks (None|str|list): None | 'precomputed' | 'auto' | [...]Dictionary with the advanced options of the map (symmetry handling, automatic landmarks, descriptor and optimization parameters). Every key is optional:
fm_params (dict)fm_params keys
Symmetry handling strategy; several strategies can be combined with + (see Symmetry-Aware Mapping):
fm_params['symmetry_mode'] (str): 'none' | 'landmarks' | 'orientation' | 'extrinsic' | 'landmarks+orientation+extrinsic'Number of spectral filters (scales) per descriptor family, and column subsampling of the point-signature blocks (recommended 3-5 when landmarks are used, since each landmark adds n_descr columns):
fm_params['n_descr'] (int) : 100
fm_params['subsample_step'] (int) : 1Options of the automatic landmark selection and of the landmark block (see Landmark Options):
fm_params['landmark_params'] (dict)Descriptor options:
fm_params['descr_params'] (dict)nu (float, 1.5): Smoothness parameter of the Matérn kernel. Lower values (e.g.,
min_l, max_l (float, None): Spatial bounds of the geometric features captured by the Matérn kernel. The min should be small enough to capture fine-grained local parts and the max should approach the bounding box diameter of the shape to capture global posture configurations. By default the range is mesh-adaptive
k_smooth (int, 30): Number of Laplace-Beltrami eigenfunctions used to low-pass filter the extrinsic descriptors XYZ and NRM.
xyz_weight (float, 0.3): Energy share of the XYZ block added automatically by symmetry_mode='extrinsic'.
Weights of the energy terms of the objective function (see Mathematical Construction Details):
fm_params['fit_params'] (dict) : {'w_descr': 1e-1, 'w_lap': 1e-3, 'w_dcomm': 1.0, 'w_orient': 1.0}Map refinement: 'auto' selects the ZoomOut parameters from the meshes, 'icp' uses ICP refinement, and a tuple (nit, step) fixes the ZoomOut iterations and step:
fm_params['refine'] (str|tuple) : 'auto' | 'icp' | (nit, step)Time step between consecutive meshes, used to report velocities and accelerations in the physical fields:
fm_params['dt'] (float) : 1.0Print the descriptor plan and the landmarks kept for every pair of meshes:
fm_params['verbose'] (bool) : FalseLandmark Options
No symmetry restrictions applied:
landmarks : NoneA priori known indices of the
landmarks (list) : [1,2,3,4,..,n] -> (n,)A priori known indices of the symmetrical vertices (one per considered transformation). If the list contains fewer sets of vertices than the pairs of meshes, the remaining computations will perform without restrictions.
landmarks (list) : [[1,..,n1],..,[1,..,nk]] -> (n,m)A priori known pair indices of the symmetrical vertices; here
landmarks (list) : [[1,2],...,[j,k]] -> (2,n)A priori known pair indices of the symmetrical vertices (one set of relations considered per transformation). If the list contains fewer sets of vertex relations than the pairs of meshes, the remaining computations will perform without restrictions.
landmarks (list) : [[[1,2],...,[j,k]],...,[[1,2],...,[l,m]]]] -> (m,2,n)Note:
The independent function precompute_landmarks(root_path,'FM') can be used along with the graphical tool to click over the vertex selection for every scene in the project. Alternatively, the function visual_selection_edition(path_to_meshes,'FM') is included to precompute or edit existing landmarks for a specific scene. Both functions will generate a landmark.npy file with the corresponding selected vertex relations, and this option will check for this precomputed .npy file during execution.
landmarks (str) : 'precomputed'Automatic landmarks. A robust set of landmarks is selected for every pair of consecutive meshes without any manual intervention:
landmarks (str) : 'auto'- Geodesic farthest-point sampling on
$M_{t_{i-1}}$ producesn_landmarkswell spread candidates, starting from the extremities (legs, head, tail), which are exactly the points that disambiguate the symmetries. - Each candidate is matched on
$M_{t_i}$ : with'hybrid'the best match in descriptor space among the vertices within a spatial radius of the landmark (the frames of a sequence are aligned), with'extrinsic'the nearest vertex in space, and with'identity'the same vertex index (meshes sharing the connectivity). - Unreliable pairs are rejected: displacement larger than
max_rel_disttimes the bounding-box diagonal, duplicated targets, and pairs that break the geodesic-distance consistency between landmarks by more thanmax_distortion(relative, removed iteratively worst-first). If fewer thanmin_landmarkspairs survive, the pair of meshes is processed without landmarks (a warning is issued).
The landmarks actually used are stored as landmarks_T0000_T0001.npy (one (m,2) array per transformation) within the folder ./PynamicMesh/Results/scene1/Landmarks/. The selection is controlled with:
fm_params['landmark_params'] (dict) : {
'n_landmarks': 12, # farthest-point samples on M_{t-1}
'match': 'hybrid', # 'hybrid' | 'extrinsic' | 'identity'
'max_rel_dist': 0.15, # max displacement (fraction of the bounding-box diagonal)
'max_distortion': 0.25, # max relative geodesic distortion between landmarks
'min_landmarks': 4, # minimum surviving pairs to use landmarks
'search_rel_radius': 0.10, # spatial search radius for 'hybrid' (fraction of the diagonal)
'weight': 1.0, # energy of the landmark block relative to the point-signature blocks
'descriptor': 'WKS', # spectral filter localized at the landmarks (default: first spectral family)
}The keys weight and descriptor also apply to explicit and 'precomputed' landmarks. Setting symmetry_mode='landmarks' with landmarks=None is equivalent to landmarks='auto'.
Symmetry-Aware Mapping
Intrinsic descriptors (WKS, HKS, MKS) are invariant under the intrinsic symmetries of a shape: if a body has a left/right isometry, the two front legs have identical signatures, and the map is determined only up to that symmetry. No purely intrinsic point descriptor can break such a symmetry, so three complementary mechanisms are provided and selected with fm_params['symmetry_mode'] (combinable with +):
Landmarks 'landmarks' : landmark-localized descriptors anchored at known or automatically selected correspondences (see Landmark Options).
Orientation term 'orientation' : adds
Extrinsic descriptors 'extrinsic' : adds the aligned coordinates of the vertices (family XYZ, low-pass filtered with k_smooth eigenfunctions) as a descriptor block with energy xyz_weight. Because consecutive frames of a sequence are spatially aligned (load_aligned_mesh) and the motion between them is small with respect to the distance between symmetric parts, the coordinates take different values on the two halves of the shape and anchor the map. The surface normals (family NRM) play the same role and both families can be written directly in the descriptor string with their own weights, e.g. descriptor='0.5*WKS + 0.3*MKS + 0.2*XYZ'.
Typical configurations:
# a) baseline: no landmarks, intrinsic descriptors only (symmetric flips possible)
run_pipeline(base_mesh_path, matrix_tranformation=True, descriptor='WKS+HKS+MKS', landmarks=None,
fm_params={'symmetry_mode': 'none'})
# b) precomputed (manually selected) landmarks only
run_pipeline(base_mesh_path, matrix_tranformation=True, descriptor='WKS+HKS+MKS', landmarks='precomputed',
fm_params={'symmetry_mode': 'landmarks'})
# c) automatic landmarks only
run_pipeline(base_mesh_path, matrix_tranformation=True, descriptor='WKS+HKS+MKS', landmarks='auto',
fm_params={'symmetry_mode': 'landmarks', 'landmark_params': {'n_landmarks': 12}})
# d) symmetry-aware descriptors only, no landmarks
run_pipeline(base_mesh_path, matrix_tranformation=True, descriptor='0.6*WKS + 0.4*MKS', landmarks=None,
fm_params={'symmetry_mode': 'orientation+extrinsic'})
# recommended default for sequences with bilateral symmetry
run_pipeline(base_mesh_path, matrix_tranformation=True, descriptor='0.6*WKS + 0.4*MKS', landmarks='auto',
fm_params={'symmetry_mode': 'landmarks+orientation+extrinsic', 'subsample_step': 4})Note:
The extrinsic and landmarks mechanisms rely on the alignment of the frames; if the meshes of a sequence are not aligned (or the motion between frames is comparable to the size of the symmetric parts), use 'precomputed' landmarks.
Landmarks Graphical Selection
For the precomputed landmarks:
from PynamicMesh.utils.visualizers import precompute_landmarks
precompute_landmarks("./PynamicMesh/Mesh_models",'FM')Vertices are chosen by clicking over them and unmarked by clicking again over the selected vertex. When the selection is ready, just close the window in order to pass to the next mesh and the pipeline is the same.
The vertex selection should be performed in the same order for each mesh so the right related pairs are formed. The selected vertices should roughly correspond to the same related regions of the meshes.
At the end, the .npy file with our vertex selection on each frame will be stored in the path ./PynamicMesh/Results/scene1/landmark.npy.
If we use the other function:
from PynamicMesh.utils.visualizers import visual_selection_edition
visual_selection_edition("./PynamicMesh/Mesh_models/scene1",'FM',source='auto')Where :
source (str): 'legacy' / 'auto' # for the .npy file of manualy selection / for the automatically generate files under Results/Scene/LandmarksThe visualizer is going to show the specific mesh dynamics and the landmarks created previously, giving the chance to edit them or also create them if they do not exist.
In this case, in order to change among meshes in time, use the arrow keys on the keyboard. When the edition is finished, just close the window and it will be stored.
Physical Features and Map Transformation Models
We can run our matrix transformation computing; at the end of the run, the pipeline will save one matrix per transformation (FMC_T0000_T0001.npy, FMV_T0001_T0000.npy; the entry ./PynamicMesh/Results/scene1/Transform_Matrices/. The indices are zero padded so that the files sort in time order.
Let's run first the pipeline without landmarks to see the results.
from PynamicMesh.core.pipelines import run_pipeline
run_pipeline(base_mesh_path, matrix_tranformation=True, descriptor='WKS+HKS', landmarks=None, k_eigenfunctions=(10,10),k_eigenvalues=100)Compute the physical fields and visualize them with:
from PynamicMesh.utils.visualizers import visualize_physics
visualize_physics("./PynamicMesh/Mesh_models/scene1", "./PynamicMesh/Results/scene1/Transform_Matrices/", on_time=False)The parameter on_time indicates if it needs to compute the physical field during execution or just look for the precomputed and stored results created during run_pipeline (compute_physic_fields=True).
on_time(bool)The fields are stored as one frame_0000.npz, frame_0001.npz, ... file per time step (the vertices, faces and every field listed below) together with the CSV global_physical_metrics.csv of integrated quantities, within the folder ./PynamicMesh/Results/scene1/Physical_fields/. The meshes are loaded with the same aligned loader used for the maps, so the fields do not include the rigid alignment of the frames.
The viewer organizes the fields in two pages: Up/Down (or p) switch the page and Left/Right step the frames. The first frame is the reference frame, where all the fields are trivial by construction (zero strains, unit stretches). Colour ranges are common to all frames (robust percentiles), so the animation is comparable in time.
All the fields are computed per vertex of
Page 1: Kinematics & classic strains
$\Delta$-Color transfer:
Showing with different colors which region of the mesh
$\Delta\vec{v}$ Vertex velocity displacement (velocity):
Showing the map over the mesh about the velocity ratios of change with respect to the vertex mapping (euclidean velocity of displacement)
Acceleration (acceleration):
Change of the displacement of the same material point between two consecutive transitions, $||\vec{d}^{,t_i}j - \vec{d}^{,t{i-1}}_{p2p(j)}||/\Delta t^2$. Highlights the regions where the motion starts, stops or changes direction (zero for a uniform motion).
Linear (edge) strain (strain):
Plot the result of the finite elements pipeline to compute the zones of stretch and compression with respect to the edges (1D),
Area (Face) strain (area_strain):
Plot the result of the finite elements pipeline to compute the zones of stretch and compression with respect to the faces (2D),
Normal protrusion flow $\vec{v}|| \vec{N}$ (normal_flow):
Signed component of the displacement along the vertex normal of
Tangent flow $\vec{v} \hookrightarrow \vec{T}$ (tangential_flow):
Subtracts the normal component from the total displacement to isolate the lateral movement,
Normal rotation (normal_rotation):
Angle (radians) between the normal of a vertex of
Mean curvature change (curvature_change):
$\Delta H_j = H^{t_i}j - H^{t{i-1}}_{p2p(j)}$, with the discrete mean curvature obtained from the cotangent Laplacian. Positive values mean the surface is becoming more convex at that point, negative values more concave (flexural deformation).
Page 2: Continuum mechanics (deformation gradient)
For every triangle the deformation gradient
Principal stretches (stretch_max, stretch_min):
Green-Lagrange principal strains (principal_strain_max, principal_strain_min):
Shear anisotropy (shear_anisotropy):
Maximum shear strain (max_shear_strain):
Elastic energy density (elastic_energy_density):
Area ratio and dilatation (area_ratio, dilatation_log):
Global metrics (global_physical_metrics.csv, one row per transition
area_ratio and volume_ratio (total surface area and enclosed volume of mean_speed (area-weighted) and max_speed, total_elastic_energy and mean_elastic_energy_density, mean_abs_strain, mean_abs_area_strain, mean_shear_anisotropy, mean_normal_flow, mean_tangential_flow, mean_normal_rotation_deg, mean_abs_curvature_change, and two quality indicators of the map: p2p_injectivity (fraction of the vertices of collapsed_faces_fraction (triangles excluded from the deformation measures). A drop of the injectivity or a rise of the collapsed fraction flags a transition where the functional map (and hence the physical fields) should not be trusted.
Plote
The Physical feature analysis provide automatically a plot profile based on it and storage the resulrs under ./Results/scene/Physical_fields/Plots.
This plots can be independly generated
from PynamicMesh.core.physic_model import plot_global_physical_metrics
plot_global_physical_metrics('Path\to\global_physical_metrics.csv')
Note:
The Camel is almost symmetric by the middle; due to this, the
The precomputed landmarks are provided here.
from PynamicMesh.core.pipelines import run_pipeline
from PynamicMesh.utils.visualizers import visualize_physics
run_pipeline(base_mesh_path, matrix_tranformation=True, descriptor='WKS+HKS', landmarks='precomputed', k_eigenfunctions=(10,10),k_eigenvalues=100)
visualize_physics("./PynamicMesh/Mesh_models/scene1", "./PynamicMesh/Results/scene1/Transform_Matrices/", on_time=False)
The same correction is obtained without any manual selection with landmarks='auto' or with fm_params={'symmetry_mode': 'orientation+extrinsic'}.
Isometry Transformation Tracking
Beside the animations and computations, within the folder (./PynamicMesh/Results/scene1/Diagonal_analysis) the Heatmap of the matrix representation in each time step will be reported. This heatmap
In our camel example, changes to the topology are not too aggressive (mostly just the legs changing positions), so the matrices stay close to the diagonal. In order to track the diagonality of the heatmaps over time, we track three metrics and report the results in a CSV file:
Moment of Inertia Metric:
where
If
If
Note: This metric is highly sensitive to far-away outliers, meaning even a small amount of energy in the far corners will cause this metric to drop significantly.
Exponential Decay Metric:
If
If
Cumulative Distribution Function Bandwidth:
The function loops through every possible bandwidth radius
Exact percentage of total energy sitting directly on the core main diagonal line.
If the plotted curve shoots up vertically and hits 1.0 at a very low bandwidth (
If the curve scales up gradually as a slow diagonal line, it indicates that the spectral energy is leaking into wide off-diagonal frequencies.
Heatmaps Similarity Metrics
A comparison among
Jensen-Shannon Divergence:
Treats the squared matrix as an "energy distribution" and measures how much the allocation of energy changes between the two mappings (base-2 logarithm, so the value lies in
For example, if a mesh was smoothly expanding over time, but suddenly starts twisting or turning, the energy distribution across the matrix will dramatically change, and JSD will spike.
Pearson & Spearman Correlation:
Measures how linearly aligned (Pearson) and structurally ranked (Spearman) the cells of
High correlation means the "nature" or "pattern" of the deformation is steady and consistent. If a mesh is undergoing a continuous, prolonged stretch in one direction over several frames, the FMs will look structurally identical. A drop in correlation means the mesh has started a new, different movement.
Manhattan $L_1$ and Euclidean $L_2$ Distances:
Measures the raw geometric difference between the specific coefficient values of the two matrices.
This acts as a measure of acceleration or intensity change. If the deformation is speeding up or becoming more drastic between frames, the coordinate distances will increase, even if the general shape of the matrix (the correlation) stays roughly the same.
Reeb Graph
General Overview
The Reeb Graph (
This is possible by assigning a vertex in the graph to each level curve, which generates a graph based on the local geometric structures.
In order to understand the dynamics of the deformation we can compute this graophs in every time step
At the end we can use this representations to have a lot of features, those are defined and explained on the correspondig Reeb Graph Usage and Analysis section
Mathematical Construction Details
Given a real scalar field over the mesh
Then the Reeb graph is the topological quotient space induced by the relation, endowed with the quotient topology
Even when the definition can be abstract, the idea is very intuitive. Think about the scalar field
The condition
Defining the equivalence relation means that we need to look at how many points of the surface
Saying that the graph has the quotient topology means that the graph captures the topology relationships of the mesh.
The idea is easy to follow graphically:
Reeb Graph Usage and Analysis
The computations are executed and managed through the syntax:
from PynamicMesh.core.pipelines import run_pipeline
run_pipeline(**args)In order to compute the Reeb Graph run:
from PynamicMesh.core.pipelines import run_pipeline
run_pipeline(
path_str='base/path',
compute_reeb=True,
time_graph_analysis=True,
reeb_scalar="geodesic",
bins=30,
**scalar_fields_args
)Or you can set your parameters on the yaml file, and within the PynamicMesh enviroment run on the comand line:
run_pynamic --config /path/to/the/config.yamlReeb Graph Parameters
Path to the root folder that contains the scenes:
path_str (str) Flag to indicate the model execution:
compute_reeb (bool)
Number of level sets used for the graph computing:
bins (int)Scalar field used to compute the graph:
reeb_scalar (str)Parameters related to the selection of Scalar Fields param_name = param_value:
**scalar_fields_args (dict)Two options are common to every scalar field. equalize_histogram replaces the field by its normalized rank in True for heat_diffusion and matern_kernel, False otherwise). geodesic_solver selects the geodesic solver for geodesic and mass_center_geodesic: 'heat' (heat method, with an automatic fallback to Dijkstra when it fails) or 'dijkstra' (exact on the edge graph, stable among frames):
equalize_histogram (bool)
geodesic_solver (str) : 'heat' | 'dijkstra'Flag to indicate if the analysis should be run within the loop; this will run the analysis over the raw computed graphs. If you need to run the analysis on the graphs after a modification, you can run it independently over the modified graphs.
time_graph_analysis (bool)Available Scalar Fields
Spatial Based
$(x,y,z)$-Level sets:
reeb_scalar="x" | reeb_scalar="y" | reeb_scalar="z"Distance from the center of mass:
reeb_scalar="dist_centroid"Signed distance relative to parallel planes crossing the centroid:
reeb_scalar="signed_dist_x" | reeb_scalar="signed_dist_y" | reeb_scalar="signed_dist_z"Absolute distance to specified axis:
reeb_scalar="dist_x_axis" | reeb_scalar="dist_y_axis" | reeb_scalar="dist_z_axis" Geometric/Topology based
Geometric mean curvature:
reeb_scalar="mean_curvature"Gaussian curvature:
reeb_scalar="gaussian_curvature"Shape base index:
reeb_scalar="shape_index"Curve base index:
reeb_scalar="curvedness"Protrusion mapping based on mesh $M_{t-1}$:
reeb_scalar="normal_displacement"Spectral mapping based on $n$ Laplace-Beltrami eigenfunctions:
reeb_scalar="lb_eigen_n" Geodesic path to the center of mass:
reeb_scalar="mass_center_geodesic"Multi scalar field maps combination through Mapper Lens construction (PCA feature extraction):
reeb_scalar="multi_pca", fields=["f1","f2",..,"fn"] Matérn Kernel:
Apply Matérn Kernel where:
Smoothness Parameter nu Controls how "differentiable" the kernel surface is.
Lower values (e.g.,
A singular landmark source_idx on the mesh is selected as the source point. The Matérn kernel measures how strongly information "diffuses" or correlates from that source point out to every other vertex.
The lengthscale referst to the spatial bounds of the geometric features to capture.
A small lengthscale isolates the scalar field strictly around your source vertex. A large lengthscale allows the correlation field to gracefully cascade over the entire body structure, giving the Reeb graph a more stable structural backbone.
reeb_scalar="matern_kernel" , nu=0.15, source_idx=i, lengthscale=1.0 source_idx options
source_idx (str|list|int)source_idx can codify different options:
Apply the same source point (
source_idx (int): nApply the mesh mass center
source_idx (str): 'mass_center'Apply the source point source_idx=0 will be applied:
source_idx (list): [0,1,2,3,...,n]Apply the source point source_idx=0 will be applied:
source_idx (list): [0,None,None,3,...,n]Visual selection, in the same fashion as in the case of the functional map (see section Landmarks graphical selection for functional maps):
source_idx (str): "precomputed"Spectral mapping based on Heat diffusion, source point (vertex index $i$) $v_i$ and time $t$:
reeb_scalar="heat_diffusion", source_idx=i, t='auto' source_idx options
source_idx (str|list|int)
t (float|str) : 'auto'The diffusion time is not scale independent: a fixed value gives an almost constant field on meshes with small eigenvalues and a Dirac-like field on meshes with large ones. t='auto' (default) selects source_idx can codify different options (a list of several indices for the same mesh yields the heat emitted by all of them):
Apply the same heat source (
source_idx (int): nApply the mesh mass center
source_idx (str): 'mass_center'Apply the heat source source_idx=0 will be applied:
source_idx (list): [0,1,2,3,...,n]Apply the heat source source_idx=0 will be applied:
source_idx (list): [0,None,None,3,...,n]Visual selection, in the same fashion as in the case of the functional map (see section Landmarks graphical selection for functional maps):
source_idx (str): "precomputed"Spectral mapping based on Harmonic with boundary conditions injection flow in vertex $v_i$ and leaving flow in vertex $v_t$:
reeb_scalar="harmonic", source_idx=i, sink_idx=t source_idx options
While in the case of source_idx=i, sink_idx=j we refer to the boundary conditions injection flow in vertex source_idx can codify different options:
source_idx (str|list|int)
sink_idx (int) Use the pair
source_idx (int)
sink_idx (int) Use every pair source_idx=i, sink_idx=j for each mesh. If there are fewer pairs than meshes, for the rest the default value source_idx=min(index), sink_idx=max(index) will be applied.
source_idx (list): [[1,2],[3,4],...,[i,j]]Use every pair source_idx=i, sink_idx=j for each mesh. For the None positions, the default value source_idx=min(index), sink_idx=max(index) will be applied.
source_idx (list): [[1,2],None,...,[i,j]]Visual selection, in the same fashion as in the case of the functional map (see section Landmarks graphical selection for functional maps).
source_idx (str): "precomputed"Geodesic mapping based on vertex landmarks $[v_0,...,v_n] \in M_t$:
reeb_scalar="geodesic", vertex_ref_index=[0,1,2,n] vertex_ref_index options
The parameter vertex_ref_index can codify different options:
vertex_ref_index (str|list)Apply the mesh mass center
vertex_ref_index (str): 'mass_center'Apply the set of reference vertices
vertex_ref_index (list): [0,1,2,3,...,n] Apply each set of reference vertices vertex_ref_index=[0] will be applied.
vertex_ref_index (list): [[0,...,n1],[0,...,n2],...,[0,...,nk]] Apply each set of reference vertices vertex_ref_index=[0] will be applied.
vertex_ref_index (list): [[0,...,n1],None,...,[0,...,nk]]Visual selection, in the same fashion as in the case of the functional map (see section Landmarks graphical selection for functional maps).
vertex_ref_index (str): "precomputed"Visual reference notes
In each case of the Heat diffusion, Harmonic, and Geodesic based scalar maps, the optional reference point can be selected graphically with the execution of the corresponding code:
from PynamicMesh.utils.visualizers import visual_selection_edition, precompute_landmarks
################################ Sources Vertex index precompute visual tools for 'heat_diffusion' in Reebs #############################################################
print('Visualizing or editing Sources Vertex index for RG...')
visual_selection_edition(mesh_path,'heat_diffusion')
print('Precomputing Sources Vertex index for RG...')
precompute_landmarks(base_mesh_path,'heat_diffusion')
################################ Source-sink Vertex index precompute visual tools for 'harmonic' in Reebs #############################################################
print('Visualizing or editing Source-sink Vertex index for RG...')
visual_selection_edition(mesh_path,'harmonic')
print('Precomputing Source-sink Vertex index for RG...')
precompute_landmarks(base_mesh_path,'harmonic')
################################# Vertex index reference precompute visual tools for 'geodesic' in Reebs #############################################################
print('Visualizing or editing Vertex index for RG...')
visual_selection_edition(mesh_path,'geodesic')
print('Precomputing Vertex index for RG...')
precompute_landmarks(base_mesh_path,'geodesic') Each one will generate and save the respective sources.npy, source_sink.npy, vert_ref_geo.npy files within the folder ./PynamicMesh/Results/scene1.
Note:
If during the pipeline execution the corresponding 'precomputed' function is used but no .npy files are found for a certain folder, the default values will be used.
Reeb Graph Visualization
After the modeling pipeline execution, the files Reeb_T0000.pkl and Scalar_T0000.npy (one for each time ./PynamicMesh/Results/scene1/Reeb_Graphs. Every node of the graph stores its position pos, its level set bin and level value f_value, and the mesh vertices it represents (n_vertices, vertices); the viewer colours the nodes by their level with the same colour map as the scalar field.
With these files, we can visualize the evolution of the field and the graph over time:
from PynamicMesh.core.pipelines import run_pipeline
from PynamicMesh.core.reeb_graph import graph_time_analysis
from PynamicMesh.utils.visualizers import visualize_reeb_graphs
print('Executing modeling ...')
run_pipeline(base_mesh_path, compute_reeb=True, bins=30 , reeb_scalar='geodesic', vertex_ref_index=[4896])
print('Reeb visualizations...')
visualize_reeb_graphs(mesh_path, reeb_path)
Reeb Graph Edition Tool
When the graphs are created, we can use the graphical tool to edit the created graph:
- Click over an existing vertex (on the graph side) to delete the vertex and all the connected edges to it.
- Click on the surface mesh vertex (on the mesh side) to create a vertex, and click on an existing graph vertex (on the graph side) to create the edge among them.
- Press key 'i' to activate INNER mode; when you click on a vertex, it moves orthogonally into the mesh (press again to deactivate).
- Press key 'o' to activate OUTER mode; when you click on a vertex, it moves orthogonally closer to the mesh surface (press again to deactivate mode).
- Press key 'c' to activate LINK mode; when you click on two existing vertices on the graph side, the edge among them is created (press again to deactivate mode).
- Space Bar to Undo the last change.
- Use 's' and 'w' keys to activate/deactivate the visible layer on the mesh.
- Use the arrow keys to change the graph in time.
When the edition is ready, just close the window. Only the corresponding modified graphs will be saved.
Note:
The original computed graphs are not overridden. The modified graphs will be stored within the folder ./PynamicMesh/Results/scene1/Reeb_graph_manual_edit.
Time Graph Analysis
When the desired graphs are ready and saved, we can run a temporal analysis and generate the plot of the results and the CSV with the data:
from PynamicMesh.core.reeb_graph import graph_time_analysis, plot_dynamic_graph_analysis
print('Graph path analysis...')
graph_time_analysis(reeb_path)
print('Plotting dynamic graph analysis...')
plot_dynamic_graph_analysis(csv_file_path)Structural Complexity (Nodes & Edges)
Encodes the raw size of the Reeb graph skeleton. This tracks how "complex" or "branchy" the shape is.
A spike in nodes and edges indicates the mesh is growing new appendages, fragmenting, or wrinkling in time.
A drop indicates the mesh is smoothing out, shrinking, or parts are merging together in time.
The "Intensity" of Deformation (The Distance Metrics)
We calculate three distance metrics (Wasserstein, Spectral Laplacian, and Graph Edit Distance) between consecutive time steps. Together, these act as an "earthquake seismograph" for the meshes.
Encodes how drastically the skeleton shifted from
Smooth, low values mean that the mesh is experiencing stable, continuous deformation (e.g., simply moving or slowly expanding).
Sudden spikes indicate a critical topological event in the system. The mesh just underwent a sudden structural change, such as breaking apart (fission), colliding/merging (fusion), or suddenly collapsing.
The Graph Edit Distance highlights direct physical breakages/additions of branches, while Spectral Distance highlights global warping of the overall shape.
Holes, Loops, and Fusions (Betti-1 Cycles)
The Betti-1 number counts the number of 1D loops or cycles in the graph. It detects when the shape folds back on itself to create a hole or a tunnel (like a donut).
An increase in cycles means appendages have touched and fused together, creating a closed loop.
Stretching and Elongation (LCC Diameter)
The "Diameter" of the Largest Connected Component represents the longest shortest-path across the graph's skeleton. It measures the maximum spatial span of the object. If the diameter steadily increases while the number of nodes stays the same, it means your mesh is being stretched or elongated (like pulling a piece of taffy).
This analysis allows us to automatically pinpoint exactly when and how your 3D meshes undergo major structural changes without having to manually watch the 3D animation. It converts visual shape evolution into a dashboard of growth (size), drastic events (distances), stretching (diameter), and fusions (cycles).
Graph Similarity Metrics
Having the graphs that enode the geometry of the transformation on the time step
from PynamicMesh.core.pipelines import run_pipeline
run_pipeline(**args)In order to track the Graph Similarity, run:
from PynamicMesh.core.pipelines import run_pipeline
run_pipeline(
path_str='base/path',
graph_sim = True,
graph_metrics = 'all'
)Or you can set your parameters on the yaml file, and within the PynamicMesh enviroment run on the comand line:
run_pynamic --config /path/to/the/config.yamlNote: Graph Similarity Metrics vs. Time Graph AnalysisWhile
Both tools evaluate the generated Reeb Graphs, they serve completely different analytical purposes:
Time Graph Analysis: Tracks consecutive animation frames
Graph Similarity Metrics (The TDA Benchmark): Focuses on the mathematical nature of the deformation rather than discrete timeline events. It uses advanced Topological Data Analysis (TDA) distances to measure continuous geometric changes. Instead of just counting broken branches, it evaluates how scalar height values shift (Interleaving Distance), how internal travel pathways warp (Function Distortion Distance), how local junctions reorganize (Degree Wasserstein), how macro-level global structures deform (Spectral Laplacian), and tracks pure topological tearing and gluing independent of the shape's scale (Branch Decomposition).
Graph Similarity Parameters
Path to the root folder that contains the scenes:
path_str (str) Flag to indicate the execution:
graph_sim (bool)Desired metrics to track:
graph_metrics (str) | (list)Available metrics
Compute and report all the available metrics :
graph_metrics (str) : 'all'Compute just the selected set of metrics :
graph_metrics (list) : ['metric_1',...,'metric_n']Degree Wasserstein Distance:
degree_wasserstein measures the difference in how "busy" or interconnected the junctions (nodes) are in the graph. This tracks local branching changes. If the mesh splits or merges, the junctions on the skeleton get more or fewer connections. A high value means the complexity of the intersections has shifted dramatically.
Spectral Laplacian Distance:
spectral_laplacian measures the global "fingerprint" or structural vibe of the graph based on its overall matrix structure. This tracks major global shape deformations. We can think of it as looking at the big picture. If the mesh undergoes a massive twist, a huge stretch, or collapses entirely, this metric will spike. It is excellent for catching massive, macro-level structural changes rather than tiny details.
Interleaving Distance & Labeled Interleaving Distance:
interleaving_distance , labeled_interleaving_distance measures the difference in the height or position (the scalar values) of the graph's features. This tracks spatial stretching or shifting along an axis. Because it looks at the positions (pos or bin) of the nodes, it tell us if the mesh is being pulled upward, compressed downward, or if features are migrating along the direction of your measurement function.
Note: Interleaving Distance = Labeled Interleaving Distance when the vertices (nodes) of the graph don't have a 'label' attribute set.
Function Distortion Distance:
function_distortion_distance measures the difference in the "shortest travel distance" between all points on the graph. This tracks elongation and structural shortening. This metric catches how much the "internal travel distance" across the mesh's skeleton is warping.
Branch Decomposition Distance:
branch_decomposition_distance measures the literal count of loops/holes (Betti numbers) and major branching points in the graph. This tracks pure topological tearing or gluing. It completely ignores how long or tall the shape is and focuses strictly on structure. If your deforming mesh rips open a new hole (like dough pulling apart) or sprouts a brand new limb, this metric registers that discrete structural change.
Batch Analysis Notes
The described methods on the previous sections allow us to configure specific sets of parameters for computing each mesh within a timelapse, or alternatively, to apply a consistent set of parameters across different timelapses. Naturally, it may be necessary to use unique parameter sets for distinct timelapses for instance, when applying different scalar fields for Reeb graphs for example or when processing timelapses of varying natures or sample types.
To better organize and manage these multi-parameter configurations, it is possible to define a unique set of parameters for each timelapse within the yaml configuration file and execute the following instruction within the conda environment.
run_pynamic --config /path/to/the/config_batch.yaml --batchOr execute programatically as
from PynamicMesh.utils.batch import run_batch
from PynamicMesh.utils.tools import extract_yaml
config_path = '\PynamicMesh\examples\config_batch.yaml'
config = extract_yaml(config_path)
data_cfg = config.get("Data", {})
path_str = data_cfg.get("path_str")
run_batch(config,path_str)







