Git Product home page Git Product logo

gpytoolbox's Introduction

A Python Geometry Processing Toolbox

unit tests unit tests unit tests PyPI

https://gpytoolbox.org

logo

Authors: Silvia Sellán, University of Toronto and Oded Stein, University of Southern California

This is a very young library of general geometry processing Python research utility functions that evolves from our personal student codebases.

Installation

Latest stable release (recommended)

You should be able install the latest release of Gpytoolbox with pip:

python -m pip install gpytoolbox

A conda installation will be supported in the future.

From Git

If you want to build Gpytoolbox from a specific git commit; for example, because you want to develop for Gpytoolbox or because you want some functionality that is in the main branch but hasn't been pushed to any release yet, you should be able to do so by cloning Gpytoolbox's github repo and running

python -m pip install numpy
python -m pip install .

Documentation

You can find documentation for all our functions in our website. You can also view the documentation for a specific function by running help(function_name) or function_name.__doc__; for example,

>>> from gpytoolbox import grad
>>> help(grad)
Finite element gradient matrix

Given a triangle mesh or a polyline, computes the finite element gradient matrix assuming piecewise linear hat function basis.

Parameters
----------
V : numpy double array
    Matrix of vertex coordinates
F : numpy int array, optional (default None)
    Matrix of triangle indices

Returns
-------
G : scipy sparse.csr_matrix
    Sparse FEM gradient matrix

See Also
--------
cotangent_laplacian.

Notes
-----

Examples
--------
TO-DO

Contribute

We hope you find our current version of our library useful. At the same time, we encourage you to ask not what Gpytoolbox can do for you, but what you can do for Gpytoolbox.

Since Gpytoolbox is a very young library, we want to make it as easy as possible for others to contribute to it and help it grow. You can contribute by adding a new function in a new file inside src/gpytoolbox/, or by adding to existing functions, and submitting a Pull Request.

We also want to make the contribution process as unintimidating as possible. We will gladly review and edit your code to make sure it acommodates to our standards and we have set up many tests that will let us know if your contribution accidentally breaks anything. If there's any functionality that is not already in this library, is remotely related to geometry processing, and you have used or used in any of your past projects, we encourage you to submit it as-is in a Pull Request. We will gladly credit you in the individual function as well as on this home page.

Note that the code that you contribute will be licensed under the MIT license. Everybody will be able to use this code as long as they credit gpytoolbox (and not you individually).

License

Gpytoolbox's is released under an MIT license (see details), except for files in the gpytoolbox.copyleft module, which are under a GPL one (see details). Functions in the copyleft module must be imported explicitly; this way, if you import only the main Gpytoolbox module

import gpytoolbox

or individual functions from it,

from gpytoolbox import regular_square_mesh, regular_cube_mesh

you are only bound by the terms of the permissive MIT license. However, if you import any functionality from gpytoolbox.copyleft; e.g.,

from gpytoolbox.copyleft import mesh_boolean

you will be bound by the more restrictive GPL license.

Attribution

If you use our library in your research paper, please cite us! You can use the bibtex block below:

@misc{gpytoolbox,
  title = {{gptyoolbox}: A Python Geometry Processing Toolbox},
  author = {Silvia Sell\'{a}n and Oded Stein and others},
  note = {https://gpytoolbox.org/},
  year = {2023}
}

Acknowledgements

Several people have, knowingly or unknowingly, greatly contributed to this library. We are thankful to them:

  • Alec Jacobson is the author of the original Matlab gptoolbox on which we inspired ourselves to create this library. Several of our functions are line-by-line translations of his Matlab ones. Thanks, Alec!

  • Nicholas Sharp, the author of the game-changing geometry visualization library polyscope, was extremely helpful in guiding us through setting up and distributing a Python package. Thanks, Nick!

Contributors

Basic Optimistic Roadmap

Here are some things we think would be nice to incorporate to future versions of gpytoolbox. If there's one you are missing, feel free to submit a PR adding your item to this bullet list. If you want to contribute to gpytoolbox, a great way to start is by picking any of the items below that does not have an associated PR yet

To-do

  • Iterative closest point for mesh alignment
  • Basic FEM (cotangent matrix, mass matrix, linear elasticity) for tetrahedral meshes
  • ARAP for deformation and parametrization
  • Exact geodesic distances
  • Heat (approximate) geodesic distance
  • Blue noise in random mesh sampling
  • Intrinsic triangulation routines
  • Fracture mode computation
  • Pure-python version of in_element_aabb
  • Make all grid sizes, resolutions, etc. into tuples not necessarily numpy arrays
  • Add notes on every docstring mentioning libigl implementations
  • Tetrahedral mesh implementation of subdivide.py
  • Dihedral angle computation
  • regular_square_mesh and regular_cube_mesh should support different resolutions in x and y direction (sensible default when n_y is None, to n_y=n_x)

gpytoolbox's People

Contributors

abhimadan avatar emjay276 avatar lsh avatar odedstein avatar otmanon avatar sgsellan avatar tovacinni avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

gpytoolbox's Issues

Marching Square gives zigzag lines

Hello, I'm using marching square function on a planar slice of sdf values, but am getting weird zigzag lines in the output. Here is the link to the sdf value and a minimal script that reproduces the image below.
image

Thank you in advance for looking into it!

NaNs in the double areas

Our doublearea_instrinsic currently contains

dblA = 0.5 * np.sqrt((a+(b+c)) * (c-(a-b)) * (c+(a-b)) * (a+(b-c)))

If the areas are very small, this can NaN. Ideally, we would do

arg = (a+(b+c)) * (c-(a-b)) * (c+(a-b)) * (a+(b-c))
dblA = 0.5 * np.sqrt(np.maximum(arg, 0.))

However, if I make this change, the unit test for reach_for_the_spheres fails.
I speculate that it is because the optimization in reach_for_the_spheres does not terminate as early anymore, so it reaches a later stage where it can crash.

What should be done about it?

error in poisson surface reconstruction edge case

I ran the function
poisson_surface_reconstruction(nerf_pts, nerf_normals, gs=np.array([50,50,50])
and it results in the error
File "<__array_function__ internals>", line 200, in ravel_multi_index ValueError: invalid entry in coordinates array

A possibly fix for it is using "mode: clip" in the np.ravel_multi_index function, or exposing that option to the user.

Upper envelope crashing (maybe only at low resolutions?)

This crashes, and it shouldn't. My current thinking is that the low resolution means there are labels that cause empty regions (e.g., labels with no associated tets). For example, I up-ed the resolution to 10, and that seems to run without issue.

import gpytoolbox, time
import numpy as np
 
labels = 4
resolution = 5
v, t = gpytoolbox.regular_cube_mesh(resolution)
w = np.zeros((v.shape[0],labels))
for i in range(labels):
    p = np.random.rand(3)
    print( "Center: ", p )
    for j in range(v.shape[0]):
        w[j,i] = -np.linalg.norm( v[j] - p )
 
print("Number of nodes: ", v.shape[0])
print("Number of labels: ", w.shape[1])
time_0 = time.time()
u, g, l = gpytoolbox.upper_envelope(v,t,w)
time_1 = time.time()
print("UE time: ", time_1-time_0)
print("Output tets: " , g.shape[0] )

The result of swept_volume if align_rotations_with_velocity is True AND The Swept Path is a Spiral

When the grinding wheel swept along the helix, I set the align_rotations_with_velocity to True and False respectively, and the result of True is a bit strange. Following is my code:

import polyscope

from test.context import gpytoolbox
from test.context import numpy as np
from test.context import unittest
from gpytoolbox import read_mesh
from gpytoolbox.copyleft import swept_volume
from gpytoolbox.copyleft import mesh_boolean
from gpytoolbox.copyleft import do_meshes_intersect

import polyscope as ps
ps.init()

# Read sample mesh
v, f = gpytoolbox.read_mesh("D:/pycharmpro/gpytoolbox/test/unit_tests_data/wheel.obj")
ps.register_surface_mesh("Wheel",v,f)

# Translation vectors to make Catmull-Rom spline
def generate_spiral_translations(radius, start_y, end_y, num_steps, pitch):
    """
    Generate translation vectors along a helical path around the y-axis,
    with specified start and end y-coordinates.

    Parameters:
    radius (float): Radius of the helix in the xz-plane.
    start_y (float): Starting y-coordinate of the helix.
    end_y (float): Ending y-coordinate of the helix.
    num_steps (int): Number of steps along the helix.
    pitch (float): Vertical distance (height) for one full rotation around the y-axis.

    Returns:
    translations (np.ndarray): Array of translation vectors (x, y, z).
    """
    # Calculate the height per step
    total_height = end_y - start_y
    h_step = total_height / num_steps

    # Initialize an empty array to store the translations
    translations = np.zeros((num_steps, 3))

    # Generate the translations
    for i in range(num_steps):
        # Calculate the angle for the current step
        # Use pitch to determine the angle per step, considering the full height of the helix
        theta = 2 * np.pi * i / (num_steps * pitch)

        # Calculate the x and z coordinates based on the angle and radius
        x = radius * np.cos(theta)
        z = radius * np.sin(theta)
        y = start_y + i * h_step  # Adjust y based on start_y and step height

        # Store the translation vector
        translations[i] = [x, y, z]

    return translations

# Example usage
radius = 0.025
start_y = -0.07
end_y = 0.07
num_steps = 50
pitch = 0.2  # Controls the number of rotations over the height

translations = generate_spiral_translations(radius, start_y, end_y, num_steps, pitch)

# Call swept volume function
u1,g1 = swept_volume(v,f,rotations=None,translations=translations,eps=0.001,verbose=True,align_rotations_with_velocity=True)
ps.register_surface_mesh("Swept_Volume1",u1,g1)

u2,g2 = swept_volume(v,f,rotations=None,translations=translations,eps=0.001,verbose=True,align_rotations_with_velocity=False)
ps.register_surface_mesh("Swept_Volume2",u2,g2)
ps.show()

The result:

align_rotations_with_velocity=True
align_rotations_with_velocity_T

align_rotations_with_velocity=False
align_rotations_with_velocity_F

It looks like one side is normal, and the other side is wrong. I don't know if it's due to my helix generation function. You can try to set a sweep with a path as a spiral and see if a similar problem occurs?
align_rotations_with_velocity

Default values for `Ft` and `Fn` if `UV` and `N` are provided

The write_mesh function can optionally take in texture coordinates UV and normals N, which require corresponding index matrices Ft and Fn, respectively. However, the function doesn't seem to check for cases where, for example, UV is provided but Ft is not provided. This results in situations where a written .obj file can contain vt rows but the f rows only contain vertex indices and no texture indices, so the file does not actually have a valid UV map.

I think a reasonable default in these cases would be to fall back on F, if there are as many texture coordinates/normals as vertices and Ft/Fn are not provided. If this isn't satisfied, I think the function should return an error. If it's preferable to avoid defaults and make the API explicit, I still think the function should return an error when UV/N is provided but Ft/Fn is not, so the semantics are clear.

All vertices are detected as boundary vertices

I tried to remesh some models from the Engineering Shape Benchmark (ESB)

For example backdoor.stl from the Flat-Thin Wallcomponents.tar.gz

The result using remesh_botsch is exactly the same as the input.

import gpytoolbox as gpy
v, f = gpy.read_mesh(r"C:\Users\Michael\Desktop\Flat-Thin-Wallcomponents\Flat-Thin Wallcomponents\Back Doors\backdoor.stl")
u, g = gpy.remesh_botsch(v, f, h=None)

I discovered all vertices of the model are detected as boundary vertices by boundary_vertices(f)

But the model has clearly some "inner" vertices (at least on the rounded part in the middle.
grafik

FEM for 3D Tet mesh

Are you interested in me implementing the FEM for 3D Tets?

I already have a working version for every element type using gmsh.
You probably want to stay with numpy / scipy only, correct?
If you don't need it next week, I can adapt it (for linear elastic tet elements) to only use numpy/scipy 😀.

  • Can you give me some hints where to find existing code/functionality for 3D Tets in gpytoolbox?

  • Do you use voigt notation for stresses/straits?
    grafik

  • Or diagonal notation for stresses/straits?
    grafik
    (only relevant for anisotropic materials)

png2poly rotates shape

Here's a png:

octopus

Running

from gpytoolbox import png2poly
poly = png2poly("octopus.png")[0]
import matplotlib.pyplot as plt
plt.plot(poly[:,0],poly[:,1])
plt.show()

for some reason produces a rotated polyline, as if the x and y coordinates were flipped:

Screen Shot 2022-09-04 at 2 34 33 PM

`signed_distance_polygon` returns nans if first polyline point is repeated

Sometimes, you'll see that a polyline is given with the last point being equal to the first to denote that it's closed. Currently, signed_distance_polygon does not support this and instead returns nan since it finds an edge with zero length. It'd be nice if the function just ignored zero-length edges instead of giving nans.

PLY file not read correctly

This issue was discovered by Jack Zhang.

Our PLY reader fails to read in the face list for a triangle mesh. libigl reads the ply just fine. If I open the triangle mesh in blender and reexport as PLY, it works though.
Simple example (the mesh files are in meshes.zip):

import gpytoolbox as gpy
import igl

V_ply,F_ply = gpy.read_mesh("mesh.ply")
print(f"V_ply: {V_ply.shape}, F_ply: {F_ply.shape}")

V_ply_igl,F_ply_igl = igl.read_triangle_mesh("mesh.ply")
print(f"V_ply_igl: {V_ply_igl.shape}, F_ply_igl: {F_ply_igl.shape}")

V_ply_blender,F_ply_blender = gpy.read_mesh("mesh_exported_by_blender.ply")
print(f"V_ply_blender: {V_ply_blender.shape}, F_ply_blender: {F_ply_blender.shape}")

V_obj,F_obj = gpy.read_mesh("mesh.obj")
print(f"V_obj: {V_obj.shape}, F_obj: {F_obj.shape}")

This produces:

V_ply: (2802, 3), F_ply: (0, 0)
V_ply_igl: (2802, 3), F_ply_igl: (934, 3)
V_ply_blender: (2802, 3), F_ply_blender: (934, 3)
V_obj: (2802, 3), F_obj: (934, 3)

I.e., gpytoolbox returns an empty triangle list on the provided mesh.ply, but not on the reexported mesh_exported_by_blender.ply.

What could cause this?
Looking at the two PLY binary files in detail, one difference that I can see is that for mesh.ply, the vertex indices are given as int, and for mesh_exported_by_blender.py the vertex indices are given as uint:

header of mesh.ply:
element face 934.property list uchar int vertex_index

header of mesh_exported_by_blender.py:
element face 934.property list uchar uint vertex_indices

Indeed, our PLY binding just seems to assume vertex indices in the face list will always be uint. Libigl's does not, they have a switch statement supporting various types.

remesh_botsch does not work for non-closed meshes

I tried to remesh a non-closed/ watertight mesh with the remesh_botsch algorithm.
Compared to a closed mesh (with almost the same size) it takes a lot longer (>100 times) and returns an error by exiting my python console.

  • Is your implementation not useable for non-closed meshes? The original paper has options for the boundary.

GMPlib certificate expiration

The SSL certificate in gmplib.org, from which libigl (and therefore us) downloads gmp, has expired, which causes curl (called to download gmp during our build) to fail. I merged #41 , a hotfix that changes our version of libigl to my PR branch sgsellan/libigl, but I am opening this issue so we have a reminder to revert this in the future when the libigl folks have converged on a solution.

2D Swept Volume/Area Computation

My understanding is that the swept volume computation does not support computing the swept area of a 2D mesh.
I would greatly appreciate if it if your implementation also supported 2D meshes. Thanks!

pip install gpytoolbox==0.1.0 gpytoolbox_bindings_copyleft

hello @sgsellan
I execute pip install gpytoolbox==0.1.0
I checked the previous version and the latest version seems to be missing something

The following problems occurred:
from gpytoolbox_bindings_copyleft import _swept_volume_impl
ImportError: DLL load failed while importing gpytoolbox_bindings_copyleft: 找不到指定的模块。

thank you!

Reach For the Sphere segmentation fault

Hey!

I'm trying to run reach for the sphere, but the code crashes on some models:

V,F = gpy.read_mesh("53159.obj")
j = 32
sdf = lambda x: gpy.signed_distance(x, V, F)[0]
gx, gy, gz = np.meshgrid(np.linspace(-1.0, 1.0, j+1), np.linspace(-1.0, 1.0, j+1), np.linspace(-1.0, 1.0, j+1))
U = np.vstack((gx.flatten(), gy.flatten(), gz.flatten())).T
V0, F0 = gpy.icosphere(2)
Vr,Fr = gpy.reach_for_the_spheres(U, sdf, V0, F0, min_h=.01)

Output:

 Segmentation fault (core dumped)

Is there some requirements on the input? The one I used (53159 of the Thingi10k dataset) is watertight and normalized to -0.8/0.8, but its genus is not zero.

libigl removed function mat_max

libigl has removed mat_max in recent versions. We use it in src/cpp/upper_envelope.cpp.

We should update our code so it will work with newer versions of libigl when we eventually upgrade.

2D RFTS broken in last update

This dumb assert assumes 3D, we should remove it

converged = gpy.reach_for_the_spheres_iteration(state)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/silviasellan/miniconda3/lib/python3.11/site-packages/gpytoolbox/reach_for_the_spheres.py", line 667, in reach_for_the_spheres_iteration
if np.any((np.isnan(state.V))) or np.any(doublearea(state.V, state.F)==0) or len(non_manifold_edges(state.F))>0 :

Exception: The file test/unit_tests_data/bunny_oded.obj could not be opened.

EEEEEEEE

ERROR: test_bunny (main.TestRemeshBotsch)

Traceback (most recent call last):
File "D:\gpytoolbox\test\test_remesh_botsch.py", line 17, in test_bunny
v,f = gpytoolbox.read_mesh("test/unit_tests_data/bunny_oded.obj")
File "D:\gpytoolbox\src\gpytoolbox\read_mesh.py", line 89, in read_mesh
V,F,UV,Ft,N,Fn = _read_obj(file,return_UV,return_N,reader)
File "D:\gpytoolbox\src\gpytoolbox\read_mesh.py", line 135, in _read_obj
raise Exception(f"The file {file} could not be opened.")
Exception: The file test/unit_tests_data/bunny_oded.obj could not be opened.

How to show the process of which a swept volume is generated?

Thank you for recommending Polyscope, it's easy to use. #128
But I want to demonstrate the process of spawning a swept body, as demonstrated in your swept_volumes' talk.
Probably because the swept_volume function in Gpytoolbox gets the result after the computation is completed, and Polyscope can't show the process of sweeping.
Do you have any suggestions?

Resolution parameters switched in documentation for torus function

The documentation for the torus function states that the first parameter (nR) is the number of vertices along the large perimeter and the second parameter (nr) is the number of vertices along the small perimeter, but in the code the meaning of these parameters are switched.

V,F = gpy.torus(10, 3, R=1., r=0.1) produces:
torcus_actual

But according to the documentation should produce:
torus_expected

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.