Skip to content
This repository was archived by the owner on Feb 3, 2023. It is now read-only.

Commit a9f3e03

Browse files
authored
Initial move over from core (#1)
There's a bunch of general math utilities in core that Mesh will depend on, core depends on. More, there's a bunch of stuff that depends on core in part just because it needs these generic math utilities. So this seemed like a good first thing to break out. This PR is really all structural, there are no interesting changes in the actual implementations.
1 parent af093ce commit a9f3e03

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+5373
-86
lines changed

README.md

Lines changed: 105 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,122 @@
1-
skeleton-py
2-
===========
3-
4-
A project starter for Body Labs Python projects.
5-
6-
To use this skeleton:
7-
8-
1. Clone the skeleton:
9-
10-
git clone [email protected]:bodylabs/template-py.git my-library
11-
12-
2. Re-initialize the git repository:
13-
14-
cd my-library && rm -rf .git && git init
15-
16-
3. Publish the license, or delete it, depending whether or not the project is
17-
open source:
18-
19-
mv RENAME-TO-LICENSE-IF-PUBLIC-ELSE-DELETE LICENSE
20-
rm RENAME-TO-LICENSE-IF-PUBLIC-ELSE-DELETE
21-
22-
4. Commit the changed files.
23-
24-
git add . && git commit -m "Project skeleton"
25-
26-
5. Make updates as needed.
27-
28-
29-
- - - - - - - - - - - - -
30-
31-
example
32-
=======
33-
34-
Short description of the module.
35-
36-
37-
Features
38-
--------
39-
40-
- This
41-
- That
42-
- The other
43-
44-
45-
Examples
46-
--------
47-
48-
```py
49-
from example.hello import hello
50-
hello()
51-
```
52-
53-
```sh
54-
hello
55-
```
1+
blmath
2+
======
3+
4+
A collection of math related utilities used by many bits of BodyLabs code.
5+
6+
Requirements
7+
------------
8+
On macOS:
9+
10+
brew install homebrew/science/suite-sparse
11+
brew install homebrew/science/opencv --without-numpy
12+
13+
On Linux:
14+
15+
sudo apt-get install python-opencv libsuitesparse-dev
16+
17+
On windows:
18+
19+
TODO: Windows install nstructions
20+
21+
22+
blmath.numerics
23+
---------------
24+
25+
Functions for manipulating numeric arrays, numbers, and linear algebra.
26+
27+
The [most commonly used of these](__init__.py) are directly imported into
28+
`blmath.numerics`.
29+
30+
- [blmath.numerics.vx](vector_shortcuts.py) is a namespace of common linear
31+
algebra operations. These are easily expressed in numpy, but abstracted for
32+
readability purposes.
33+
- [blmath.numerics.coercion](coercion.py) contains a validation function
34+
`as_numeric_array`, which produces useful error messages up front on bad
35+
inputs, in place of cryptic messages like "cannot broadcast..." later on.
36+
- [blmath.numerics.operations](operations.py) contains basic numerical
37+
operations such as `zero_safe_divide`.
38+
- [blmath.numerics.predicates](predicates.py) contains functions like
39+
`isnumeric`.
40+
- [blmath.numerics.rounding](rounding.py) contains functions including
41+
"round to nearest" and `roundedlist`.
42+
- [blmath.numerics.numpy_ext](numpy_ext.py) contains numpy utility
43+
functions.
44+
- [blmath.numerics.matlab](matlab.py) contains some matlab shortcuts which
45+
have no numpy equivalent. At MPI the fitting code was originally written in
46+
Matlab before it was ported to Python.
47+
48+
[blmath.numerics.linalg](linalg) contains linear algebra operations.
49+
50+
- [blmath.numerics.linalg.sparse_cg](linalg/sparse_cg.py) contains a faster
51+
matrix solve optimized for sparse Jacobians.
52+
- [blmath.numerics.linalg.lchol](linalg/lchol.py) contains a Cythonized
53+
implementation of Cholesky factorization.
54+
- [blmath.numerics.linalg.isomorphism](linalg/isomorphism.py) computes the
55+
isomorphism between two bases.
56+
- [blmath.numerics.linalg.gram_schmidt](linalg/gram_schmidt.py) provides a
57+
function for orthonormalization.
58+
59+
blmath.geometry
60+
---------------
61+
62+
Geometric operations, transforms, and primitives, in 2D and 3D.
63+
64+
The [most commonly used of these](__init__.py) are directly imported into
65+
`blmath.geometry`.
66+
67+
- [blmath.geometry.Box](primitives/box.py) represents an axis-aligned
68+
cuboid.
69+
- [blmath.geometry.Plane](primitives/plane.py) represents a 2-D plane in
70+
3-space (not a hyperplane).
71+
- [blmath.geometry.Polyline](primitives/polyline.py) represents an
72+
unconstrained polygonal chain in 3-space.
73+
74+
`blmath.geometry.transform` includes code for 3D transforms.
75+
76+
- [blmath.geometry.transform.CompositeTransform](transform/composite.py)
77+
represents a composite transform using homogeneous coordinates. (Thanks avd!)
78+
- [blmath.geometry.transform.CoordinateManager](transform/coordinate_manager.py)
79+
provides a convenient interface for named reference frames within a stack of
80+
transforms and projecting points from one reference frame to another.
81+
- [blmath.geometry.transform.find_rigid_transform](transform/rigid_transform.py)
82+
finds a rotation and translation that closely transforms one set of points to
83+
another. Its cousin `find_rigid_rotation` does the same, but only allows
84+
rotation, not translation.
85+
- [blmath.geometry.transform.rotation.rotation_from_up_and_look](transform/rotation.py)
86+
produces a rotation matrix that gets a mesh into the canonical reference frame
87+
from "up" and "look" vectors.
88+
89+
Other modules:
90+
91+
- [blmath.geometry.apex](apex.py) provides functions for finding the most
92+
extreme point.
93+
- [blmath.geometry.barycentric](barycentric.py) provides a function for
94+
projecting a point to a triangle using barycentric coordinates.
95+
- [blmath.geometry.convexify](convexify.py) provides a function for
96+
producing a convex hull from a mostly-planar curve.
97+
- [blmath.geometry.segment](segment.py) provides functions for working with
98+
line segments in n-space.
99+
100+
blmath.units
101+
------------
102+
TODO write something here
56103

57104

58105
Development
59106
-----------
60107

61108
```sh
62109
pip install -r requirements_dev.txt
63-
rake test
110+
rake unittest
64111
rake lint
65112
```
66113

67114

68115
Contribute
69116
----------
70117

71-
- Issue Tracker: github.com/bodylabs/example/issues
72-
- Source Code: github.com/bodylabs/example
118+
- Issue Tracker: github.com/bodylabs/blmath/issues
119+
- Source Code: github.com/bodylabs/blmath
73120

74121
Pull requests welcome!
75122

Rakefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ task :unittest do
4141
end
4242

4343
task :lint => :require_style_config do
44-
raise unless system "bodylabs-python-style/bin/pylint_test example --min_rating 10.0"
44+
raise unless system "bodylabs-python-style/bin/pylint_test blmath --min_rating 10.0"
4545
end
4646

4747
desc "Remove .pyc files"

bin/hello

Lines changed: 0 additions & 4 deletions
This file was deleted.
File renamed without changes.

blmath/cache.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import os
2+
from baiji.pod import AssetCache, VersionedCache
3+
from baiji.pod.config import Config
4+
5+
config = Config()
6+
config.CACHE_DIR = os.path.expanduser('~/.bodylabs_static_cache')
7+
config.IMMUTABLE_BUCKETS = ['bodylabs-versioned-assets', 'bodylabs-versioned-assets-tokyo']
8+
config.DEFAULT_BUCKET = os.getenv('ASSET_BUCKET', 'bodylabs-assets')
9+
10+
sc = AssetCache(config)
11+
12+
MANIFEST_PATH = os.path.join(os.path.dirname(__file__), '..', 'manifest.json')
13+
BUCKET = os.getenv('VC_BUCKET', 'bodylabs-versioned-assets')
14+
15+
vc = VersionedCache(
16+
cache=sc,
17+
manifest_path=MANIFEST_PATH,
18+
bucket=BUCKET)

blmath/geometry/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from blmath.geometry.primitives.box import Box
2+
from blmath.geometry.primitives.plane import Plane
3+
from blmath.geometry.primitives.polyline import Polyline

blmath/geometry/apex.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import numpy as np
2+
from blmath.numerics import vx
3+
4+
def apex(points, axis):
5+
'''
6+
Find the most extreme point in the direction of the axis provided.
7+
8+
axis: A vector, which is an 3x1 np.array.
9+
10+
'''
11+
coords_on_axis = points.dot(axis)
12+
return points[np.argmax(coords_on_axis)]
13+
14+
def inflection_points(points, axis, span):
15+
'''
16+
Find the list of vertices that preceed inflection points in a curve. The curve is differentiated
17+
with respect to the coordinate system defined by axis and span.
18+
19+
axis: A vector representing the vertical axis of the coordinate system.
20+
span: A vector representing the the horiztonal axis of the coordinate system.
21+
22+
returns: a list of points in space corresponding to the vertices that
23+
immediately preceed inflection points in the curve
24+
'''
25+
26+
coords_on_span = points.dot(span)
27+
dx = np.gradient(coords_on_span)
28+
coords_on_axis = points.dot(axis)
29+
30+
# Take the second order finite difference of the curve with respect to the
31+
# defined coordinate system
32+
finite_difference_2 = np.gradient(np.gradient(coords_on_axis, dx), dx)
33+
34+
# Compare the product of all neighboring pairs of points in the second derivative
35+
# If a pair of points has a negative product, then the second derivative changes sign
36+
# at one of those points, signalling an inflection point
37+
is_inflection_point = [finite_difference_2[i] * finite_difference_2[i + 1] <= 0 for i in range(len(finite_difference_2) - 1)]
38+
39+
inflection_point_indices = [i for i, b in enumerate(is_inflection_point) if b]
40+
41+
if len(inflection_point_indices) == 0:
42+
return []
43+
44+
return points[inflection_point_indices]
45+
46+
def farthest(from_point, to_points):
47+
'''
48+
Find the farthest point among the inputs, to the given point.
49+
50+
Return a tuple: farthest_point, index_of_farthest_point.
51+
'''
52+
absolute_distances = vx.magnitude(to_points - from_point)
53+
54+
index_of_farthest_point = np.argmax(absolute_distances)
55+
farthest_point = to_points[index_of_farthest_point]
56+
57+
return farthest_point, index_of_farthest_point

blmath/geometry/barycentric.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import numpy as np
2+
3+
def barycentric_coordinates_of_projection(p, q, u, v):
4+
""" Given a point, gives projected coords of that point to a triangle
5+
in barycentric coordinates.
6+
7+
See Heidrich, Computing the Barycentric Coordinates of a Projected Point, JGT 05
8+
at http://www.cs.ubc.ca/~heidrich/Papers/JGT.05.pdf
9+
10+
Args:
11+
p: point to project
12+
q: a vertex of the triangle to project into
13+
u,v: edges of the the triangle such that it has vertices q, q+u, q+v
14+
15+
Returns:
16+
b: barycentric coordinates of p's projection in triangle defined by q,u,v
17+
vectorized so p,q,u,v can all be 3xN
18+
"""
19+
20+
p = p.T
21+
q = q.T
22+
u = u.T
23+
v = v.T
24+
25+
n = np.cross(u, v, axis=0)
26+
s = np.sum(n*n, axis=0)
27+
28+
# If the triangle edges are collinear, cross-product is zero,
29+
# which makes "s" 0, which gives us divide by zero. So we
30+
# make the arbitrary choice to set s to epsv (=numpy.spacing(1)),
31+
# the closest thing to zero
32+
if np.isscalar(s):
33+
s = s if s else np.spacing(1)
34+
else:
35+
s[s == 0] = np.spacing(1)
36+
37+
oneOver4ASquared = 1.0 / s
38+
w = p - q
39+
b2 = np.sum(np.cross(u, w, axis=0) * n, axis=0) * oneOver4ASquared
40+
b1 = np.sum(np.cross(w, v, axis=0) * n, axis=0) * oneOver4ASquared
41+
b = np.vstack((1 - b1 - b2, b1, b2))
42+
43+
return b.T

blmath/geometry/convexify.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
def convexify_planar_curve(polyline, flatten=False, want_vertices=False):
2+
'''
3+
Take the convex hull of an almost planar 2-D curve in three-space.
4+
5+
Params:
6+
- polyline:
7+
An instance of Polyline.
8+
- flatten:
9+
Boolean; if True, rotated curve will be flattened
10+
on the y-axis, thereby losing length. This loss
11+
may not be offset by the convex hull.
12+
- want_vertices:
13+
Boolean; do you want the indices to the convex hull vertices?
14+
'''
15+
import numpy as np
16+
from scipy.spatial import ConvexHull # First thought this warning was caused by a pythonpath problem, but it seems more likely that the warning is caused by scipy import hackery. pylint: disable=no-name-in-module
17+
from blmath.geometry import Polyline
18+
from blmath.geometry.transform.rotation import rotate_to_xz_plane
19+
from blmath.geometry.transform.translation import translation
20+
21+
if len(polyline.v) <= 1:
22+
return polyline
23+
24+
rotated, R, p0 = rotate_to_xz_plane(polyline.v)
25+
26+
if flatten:
27+
rotated[:, 1] = np.mean(rotated[:, 1])
28+
29+
projected = rotated[:, [0, 2]]
30+
31+
hull_vertices = ConvexHull(projected).vertices
32+
rotated_hull = rotated[hull_vertices]
33+
34+
R_inv = np.array(np.mat(R.T).I)
35+
inv_rotated = np.dot(rotated_hull, R_inv)
36+
37+
if p0 is not None:
38+
curve_hull = translation(np.array(inv_rotated), -p0)[0]
39+
else:
40+
curve_hull = inv_rotated
41+
42+
result = Polyline(v=curve_hull, closed=polyline.closed)
43+
44+
if want_vertices:
45+
return result, hull_vertices
46+
else:
47+
return result

0 commit comments

Comments
 (0)