swcgeom 0.18.3__py3-none-any.whl → 0.19.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of swcgeom might be problematic. Click here for more details.
- swcgeom/analysis/feature_extractor.py +22 -24
- swcgeom/analysis/features.py +18 -40
- swcgeom/analysis/lmeasure.py +227 -323
- swcgeom/analysis/sholl.py +17 -23
- swcgeom/analysis/trunk.py +23 -28
- swcgeom/analysis/visualization.py +37 -44
- swcgeom/analysis/visualization3d.py +16 -25
- swcgeom/analysis/volume.py +33 -47
- swcgeom/core/__init__.py +1 -6
- swcgeom/core/branch.py +10 -17
- swcgeom/core/branch_tree.py +3 -2
- swcgeom/core/compartment.py +1 -1
- swcgeom/core/node.py +3 -6
- swcgeom/core/path.py +11 -16
- swcgeom/core/population.py +32 -51
- swcgeom/core/swc.py +25 -16
- swcgeom/core/swc_utils/__init__.py +4 -6
- swcgeom/core/swc_utils/assembler.py +5 -12
- swcgeom/core/swc_utils/base.py +40 -31
- swcgeom/core/swc_utils/checker.py +3 -8
- swcgeom/core/swc_utils/io.py +32 -47
- swcgeom/core/swc_utils/normalizer.py +17 -23
- swcgeom/core/swc_utils/subtree.py +13 -20
- swcgeom/core/tree.py +61 -51
- swcgeom/core/tree_utils.py +36 -49
- swcgeom/core/tree_utils_impl.py +4 -6
- swcgeom/images/augmentation.py +23 -39
- swcgeom/images/contrast.py +22 -46
- swcgeom/images/folder.py +32 -34
- swcgeom/images/io.py +80 -121
- swcgeom/transforms/base.py +28 -19
- swcgeom/transforms/branch.py +31 -41
- swcgeom/transforms/branch_tree.py +3 -1
- swcgeom/transforms/geometry.py +13 -4
- swcgeom/transforms/image_preprocess.py +2 -0
- swcgeom/transforms/image_stack.py +40 -35
- swcgeom/transforms/images.py +31 -24
- swcgeom/transforms/mst.py +27 -40
- swcgeom/transforms/neurolucida_asc.py +13 -13
- swcgeom/transforms/path.py +4 -0
- swcgeom/transforms/population.py +4 -0
- swcgeom/transforms/tree.py +16 -11
- swcgeom/transforms/tree_assembler.py +37 -54
- swcgeom/utils/download.py +7 -14
- swcgeom/utils/dsu.py +12 -0
- swcgeom/utils/ellipse.py +26 -14
- swcgeom/utils/file.py +8 -13
- swcgeom/utils/neuromorpho.py +78 -92
- swcgeom/utils/numpy_helper.py +15 -12
- swcgeom/utils/plotter_2d.py +10 -16
- swcgeom/utils/plotter_3d.py +7 -9
- swcgeom/utils/renderer.py +16 -8
- swcgeom/utils/sdf.py +12 -23
- swcgeom/utils/solid_geometry.py +58 -2
- swcgeom/utils/transforms.py +164 -100
- swcgeom/utils/volumetric_object.py +29 -53
- {swcgeom-0.18.3.dist-info → swcgeom-0.19.0.dist-info}/METADATA +5 -4
- swcgeom-0.19.0.dist-info/RECORD +67 -0
- {swcgeom-0.18.3.dist-info → swcgeom-0.19.0.dist-info}/WHEEL +1 -1
- swcgeom-0.18.3.dist-info/RECORD +0 -67
- {swcgeom-0.18.3.dist-info → swcgeom-0.19.0.dist-info/licenses}/LICENSE +0 -0
- {swcgeom-0.18.3.dist-info → swcgeom-0.19.0.dist-info}/top_level.txt +0 -0
swcgeom/utils/plotter_2d.py
CHANGED
|
@@ -15,8 +15,6 @@
|
|
|
15
15
|
|
|
16
16
|
"""2D Plotting utils."""
|
|
17
17
|
|
|
18
|
-
from typing import Optional
|
|
19
|
-
|
|
20
18
|
import matplotlib.pyplot as plt
|
|
21
19
|
import numpy as np
|
|
22
20
|
import numpy.typing as npt
|
|
@@ -43,18 +41,14 @@ def draw_lines(
|
|
|
43
41
|
) -> LineCollection:
|
|
44
42
|
"""Draw lines.
|
|
45
43
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
Camera position.
|
|
54
|
-
**kwargs : dict[str, Unknown]
|
|
55
|
-
Forwarded to `~matplotlib.collections.LineCollection`.
|
|
44
|
+
Args:
|
|
45
|
+
ax: The plot axes.
|
|
46
|
+
lines: A collection of coords of lines
|
|
47
|
+
Excepting a ndarray of shape (N, 2, 3), the axis-2 holds two points,
|
|
48
|
+
and the axis-3 holds the coordinates (x, y, z).
|
|
49
|
+
camera: Camera position.
|
|
50
|
+
**kwargs: Forwarded to `~matplotlib.collections.LineCollection`.
|
|
56
51
|
"""
|
|
57
|
-
|
|
58
52
|
T = camera.MVP
|
|
59
53
|
T = translate3d(*camera.position).dot(T) # keep origin
|
|
60
54
|
|
|
@@ -113,8 +107,8 @@ def draw_circles(
|
|
|
113
107
|
x: npt.NDArray,
|
|
114
108
|
y: npt.NDArray,
|
|
115
109
|
*,
|
|
116
|
-
y_min:
|
|
117
|
-
y_max:
|
|
110
|
+
y_min: float | None = None,
|
|
111
|
+
y_max: float | None = None,
|
|
118
112
|
cmap: str | Colormap = "viridis",
|
|
119
113
|
) -> PatchCollection:
|
|
120
114
|
"""Draw a sequential of circles."""
|
|
@@ -140,7 +134,7 @@ def draw_circles(
|
|
|
140
134
|
|
|
141
135
|
|
|
142
136
|
def get_fig_ax(
|
|
143
|
-
fig:
|
|
137
|
+
fig: Figure | None = None, ax: Axes | None = None
|
|
144
138
|
) -> tuple[Figure, Axes]:
|
|
145
139
|
if fig is None and ax is not None:
|
|
146
140
|
fig = ax.get_figure()
|
swcgeom/utils/plotter_3d.py
CHANGED
|
@@ -32,17 +32,15 @@ def draw_lines_3d(
|
|
|
32
32
|
):
|
|
33
33
|
"""Draw lines.
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
**kwargs : dict[str, Unknown]
|
|
42
|
-
Forwarded to `~mpl_toolkits.mplot3d.art3d.Line3DCollection`.
|
|
35
|
+
Args:
|
|
36
|
+
ax: The plot axes.
|
|
37
|
+
lines: A collection of coords of lines
|
|
38
|
+
Excepting a ndarray of shape (N, 2, 3), the axis-2 holds two points,
|
|
39
|
+
and the axis-3 holds the coordinates (x, y, z).
|
|
40
|
+
**kwargs: Forwarded to `~mpl_toolkits.mplot3d.art3d.Line3DCollection`.
|
|
43
41
|
"""
|
|
44
42
|
|
|
45
43
|
line_collection = Line3DCollection(
|
|
46
44
|
lines, joinstyle=joinstyle, capstyle=capstyle, **kwargs
|
|
47
|
-
)
|
|
45
|
+
)
|
|
48
46
|
return ax.add_collection3d(line_collection)
|
swcgeom/utils/renderer.py
CHANGED
|
@@ -48,21 +48,29 @@ class Camera:
|
|
|
48
48
|
_look_at: Vec3f
|
|
49
49
|
_up: Vec3f
|
|
50
50
|
|
|
51
|
-
# fmt: off
|
|
52
51
|
@property
|
|
53
|
-
def position(self) -> Vec3f:
|
|
52
|
+
def position(self) -> Vec3f:
|
|
53
|
+
return self._position
|
|
54
|
+
|
|
54
55
|
@property
|
|
55
|
-
def look_at(self) -> Vec3f:
|
|
56
|
+
def look_at(self) -> Vec3f:
|
|
57
|
+
return self._look_at
|
|
58
|
+
|
|
56
59
|
@property
|
|
57
|
-
def up(self) -> Vec3f:
|
|
60
|
+
def up(self) -> Vec3f:
|
|
61
|
+
return self._up
|
|
58
62
|
|
|
59
63
|
@property
|
|
60
|
-
def MV(self) -> npt.NDArray[np.float32]:
|
|
64
|
+
def MV(self) -> npt.NDArray[np.float32]:
|
|
65
|
+
raise NotImplementedError()
|
|
66
|
+
|
|
61
67
|
@property
|
|
62
|
-
def P(self) -> npt.NDArray[np.float32]:
|
|
68
|
+
def P(self) -> npt.NDArray[np.float32]:
|
|
69
|
+
raise NotImplementedError()
|
|
70
|
+
|
|
63
71
|
@property
|
|
64
|
-
def MVP(self) -> npt.NDArray[np.float32]:
|
|
65
|
-
|
|
72
|
+
def MVP(self) -> npt.NDArray[np.float32]:
|
|
73
|
+
return self.P.dot(self.MV)
|
|
66
74
|
|
|
67
75
|
|
|
68
76
|
class SimpleCamera(Camera):
|
swcgeom/utils/sdf.py
CHANGED
|
@@ -17,10 +17,8 @@
|
|
|
17
17
|
|
|
18
18
|
Refs: https://iquilezles.org/articles/distfunctions/
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
This module has been deprecated since v0.14.0, and will be removed in
|
|
23
|
-
the future, use `sdflit` instead.
|
|
20
|
+
NOTE: This module has been deprecated since v0.14.0, and will be removed in the future,
|
|
21
|
+
use `sdflit` instead.
|
|
24
22
|
"""
|
|
25
23
|
|
|
26
24
|
import warnings
|
|
@@ -60,15 +58,11 @@ class SDF(ABC):
|
|
|
60
58
|
def distance(self, p: npt.NDArray[np.float32]) -> npt.NDArray[np.float32]:
|
|
61
59
|
"""Calculate signed distance.
|
|
62
60
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
p: ArrayLike
|
|
66
|
-
Hit point p of shape (N, 3).
|
|
61
|
+
Args:
|
|
62
|
+
p: Hit point p of shape (N, 3).
|
|
67
63
|
|
|
68
|
-
Returns
|
|
69
|
-
|
|
70
|
-
distance : npt.NDArray[np.float32]
|
|
71
|
-
Distance array of shape (3,).
|
|
64
|
+
Returns:
|
|
65
|
+
distance: Distance array of shape (3,).
|
|
72
66
|
"""
|
|
73
67
|
raise NotImplementedError()
|
|
74
68
|
|
|
@@ -84,11 +78,9 @@ class SDF(ABC):
|
|
|
84
78
|
def is_in_bounding_box(self, p: npt.NDArray[np.float32]) -> npt.NDArray[np.bool_]:
|
|
85
79
|
"""Is p in bounding box.
|
|
86
80
|
|
|
87
|
-
Returns
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
Array of shape (N,), if bounding box is `None`, `True` will
|
|
91
|
-
be returned.
|
|
81
|
+
Returns:
|
|
82
|
+
is_in: Array of shape (N,).
|
|
83
|
+
If bounding box is `None`, `True` will be returned.
|
|
92
84
|
"""
|
|
93
85
|
|
|
94
86
|
if self.bounding_box is None:
|
|
@@ -285,12 +277,9 @@ class SDFRoundCone(SDF):
|
|
|
285
277
|
) -> None:
|
|
286
278
|
"""SDF of round cone.
|
|
287
279
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
Coordinates of point A/B of shape (3,).
|
|
292
|
-
ra, rb : float
|
|
293
|
-
Radius of point A/B.
|
|
280
|
+
Args:
|
|
281
|
+
a, b: Coordinates of point A/B of shape (3,).
|
|
282
|
+
ra, rb: Radius of point A/B.
|
|
294
283
|
"""
|
|
295
284
|
|
|
296
285
|
self.a = np.array(a, dtype=np.float32)
|
swcgeom/utils/solid_geometry.py
CHANGED
|
@@ -28,6 +28,15 @@ __all__ = [
|
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
def find_unit_vector_on_plane(normal_vec3: npt.NDArray) -> npt.NDArray:
|
|
31
|
+
"""Find a random unit vector on the plane defined by the normal vector.
|
|
32
|
+
|
|
33
|
+
>>> normal = np.array([0, 0, 1])
|
|
34
|
+
>>> u = find_unit_vector_on_plane(normal)
|
|
35
|
+
>>> np.allclose(np.dot(u, normal), 0) # Should be perpendicular
|
|
36
|
+
True
|
|
37
|
+
>>> np.allclose(np.linalg.norm(u), 1) # Should be unit length
|
|
38
|
+
True
|
|
39
|
+
"""
|
|
31
40
|
r = np.random.rand(3)
|
|
32
41
|
r /= np.linalg.norm(r)
|
|
33
42
|
while np.allclose(r, normal_vec3) or np.allclose(r, -normal_vec3):
|
|
@@ -45,6 +54,21 @@ def find_sphere_line_intersection(
|
|
|
45
54
|
line_point_a: npt.NDArray,
|
|
46
55
|
line_point_b: npt.NDArray,
|
|
47
56
|
) -> list[tuple[float, npt.NDArray[np.float64]]]:
|
|
57
|
+
"""Find intersection points between a sphere and a line.
|
|
58
|
+
|
|
59
|
+
>>> center = np.array([0, 0, 0])
|
|
60
|
+
>>> radius = 1.0
|
|
61
|
+
>>> p1 = np.array([-2, 0, 0])
|
|
62
|
+
>>> p2 = np.array([2, 0, 0])
|
|
63
|
+
>>> intersections = find_sphere_line_intersection(center, radius, p1, p2)
|
|
64
|
+
>>> len(intersections)
|
|
65
|
+
2
|
|
66
|
+
>>> np.allclose(intersections[0][1], [-1, 0, 0])
|
|
67
|
+
True
|
|
68
|
+
>>> np.allclose(intersections[1][1], [1, 0, 0])
|
|
69
|
+
True
|
|
70
|
+
"""
|
|
71
|
+
|
|
48
72
|
A = np.array(line_point_a)
|
|
49
73
|
B = np.array(line_point_b)
|
|
50
74
|
C = np.array(sphere_center)
|
|
@@ -74,8 +98,20 @@ def find_sphere_line_intersection(
|
|
|
74
98
|
|
|
75
99
|
|
|
76
100
|
def project_point_on_line(
|
|
77
|
-
point_a: npt.ArrayLike,
|
|
101
|
+
point_a: npt.ArrayLike,
|
|
102
|
+
direction_vector: npt.ArrayLike,
|
|
103
|
+
point_p: npt.ArrayLike,
|
|
78
104
|
) -> npt.NDArray:
|
|
105
|
+
"""Project a point onto a line defined by a point and direction vector.
|
|
106
|
+
|
|
107
|
+
>>> a = np.array([0, 0, 0])
|
|
108
|
+
>>> d = np.array([1, 0, 0])
|
|
109
|
+
>>> p = np.array([1, 1, 0])
|
|
110
|
+
>>> projection = project_point_on_line(a, d, p)
|
|
111
|
+
>>> np.allclose(projection, [1, 0, 0])
|
|
112
|
+
True
|
|
113
|
+
"""
|
|
114
|
+
|
|
79
115
|
A = np.array(point_a)
|
|
80
116
|
n = np.array(direction_vector)
|
|
81
117
|
P = np.array(point_p)
|
|
@@ -86,6 +122,14 @@ def project_point_on_line(
|
|
|
86
122
|
|
|
87
123
|
|
|
88
124
|
def project_vector_on_vector(vec: npt.ArrayLike, target: npt.ArrayLike) -> npt.NDArray:
|
|
125
|
+
"""Project one vector onto another.
|
|
126
|
+
|
|
127
|
+
>>> v = np.array([1, 1, 0])
|
|
128
|
+
>>> t = np.array([1, 0, 0])
|
|
129
|
+
>>> proj = project_vector_on_vector(v, t)
|
|
130
|
+
>>> np.allclose(proj, [1, 0, 0])
|
|
131
|
+
True
|
|
132
|
+
"""
|
|
89
133
|
v = np.array(vec)
|
|
90
134
|
n = np.array(target)
|
|
91
135
|
|
|
@@ -95,8 +139,20 @@ def project_vector_on_vector(vec: npt.ArrayLike, target: npt.ArrayLike) -> npt.N
|
|
|
95
139
|
|
|
96
140
|
|
|
97
141
|
def project_vector_on_plane(
|
|
98
|
-
vec: npt.ArrayLike,
|
|
142
|
+
vec: npt.ArrayLike,
|
|
143
|
+
plane_normal_vec: npt.ArrayLike,
|
|
99
144
|
) -> npt.NDArray:
|
|
145
|
+
"""Project a vector onto a plane defined by its normal vector.
|
|
146
|
+
|
|
147
|
+
>>> v = np.array([1, 1, 1])
|
|
148
|
+
>>> n = np.array([0, 0, 1])
|
|
149
|
+
>>> proj = project_vector_on_plane(v, n)
|
|
150
|
+
>>> np.allclose(proj, [1, 1, 0]) # Z component removed
|
|
151
|
+
True
|
|
152
|
+
>>> np.allclose(np.dot(proj, n), 0) # Should be perpendicular to normal
|
|
153
|
+
True
|
|
154
|
+
"""
|
|
155
|
+
|
|
100
156
|
v = np.array(vec)
|
|
101
157
|
n = np.array(plane_normal_vec)
|
|
102
158
|
|
swcgeom/utils/transforms.py
CHANGED
|
@@ -36,26 +36,56 @@ Vec3f = tuple[float, float, float]
|
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
def angle(a: npt.ArrayLike, b: npt.ArrayLike) -> float:
|
|
39
|
-
"""Get the angle
|
|
39
|
+
"""Get the signed angle between two vectors.
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
The angle is positive if the rotation from a to b is counter-clockwise, and
|
|
42
|
+
negative if clockwise.
|
|
43
|
+
|
|
44
|
+
>>> angle([1, 0, 0], [1, 0, 0]) # identical
|
|
45
|
+
0.0
|
|
46
|
+
>>> angle([1, 0, 0], [0, 1, 0]) # 90 degrees counter-clockwise
|
|
47
|
+
1.5707963267948966
|
|
48
|
+
>>> angle([1, 0, 0], [0, -1, 0]) # 90 degrees clockwise
|
|
49
|
+
-1.5707963267948966
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
angle: Angle in radians between -π and π.
|
|
45
53
|
"""
|
|
46
|
-
a, b = np.array(a), np.array(b)
|
|
47
|
-
costheta = np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
|
|
48
|
-
theta = np.arccos(costheta)
|
|
49
|
-
return theta if np.cross(a, b) > 0 else -theta
|
|
50
54
|
|
|
55
|
+
a = np.asarray(a)
|
|
56
|
+
b = np.asarray(b)
|
|
51
57
|
|
|
52
|
-
|
|
53
|
-
|
|
58
|
+
# Normalize vectors
|
|
59
|
+
a_norm = a / np.linalg.norm(a)
|
|
60
|
+
b_norm = b / np.linalg.norm(b)
|
|
61
|
+
|
|
62
|
+
# Calculate cosine of angle
|
|
63
|
+
cos_theta = np.dot(a_norm, b_norm)
|
|
64
|
+
cos_theta = np.clip(cos_theta, -1.0, 1.0) # Ensure within valid range
|
|
65
|
+
theta = np.arccos(cos_theta)
|
|
66
|
+
|
|
67
|
+
# Determine sign using cross product
|
|
68
|
+
cross = np.cross(a_norm, b_norm)
|
|
69
|
+
sign = np.sign(cross[2]) # Use z-component for 3D
|
|
70
|
+
return float(sign * theta)
|
|
54
71
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
72
|
+
|
|
73
|
+
def scale3d(sx: float, sy: float, sz: float) -> npt.NDArray[np.float32]:
|
|
74
|
+
"""Get the 3D scale transformation matrix.
|
|
75
|
+
|
|
76
|
+
>>> np.allclose(
|
|
77
|
+
... scale3d(2, 3, 4),
|
|
78
|
+
... [
|
|
79
|
+
... [2.0, 0.0, 0.0, 0.0],
|
|
80
|
+
... [0.0, 3.0, 0.0, 0.0],
|
|
81
|
+
... [0.0, 0.0, 4.0, 0.0],
|
|
82
|
+
... [0.0, 0.0, 0.0, 1.0],
|
|
83
|
+
... ],
|
|
84
|
+
... )
|
|
85
|
+
True
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
T: The homogeneous transformation matrix, shape (4, 4).
|
|
59
89
|
"""
|
|
60
90
|
return np.array(
|
|
61
91
|
[
|
|
@@ -69,12 +99,21 @@ def scale3d(sx: float, sy: float, sz: float) -> npt.NDArray[np.float32]:
|
|
|
69
99
|
|
|
70
100
|
|
|
71
101
|
def translate3d(tx: float, ty: float, tz: float) -> npt.NDArray[np.float32]:
|
|
72
|
-
"""Get the 3D translate
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
102
|
+
"""Get the 3D translate transformation matrix.
|
|
103
|
+
|
|
104
|
+
>>> np.allclose(
|
|
105
|
+
... translate3d(1, 2, 3),
|
|
106
|
+
... [
|
|
107
|
+
... [1.0, 0.0, 0.0, 1.0],
|
|
108
|
+
... [0.0, 1.0, 0.0, 2.0],
|
|
109
|
+
... [0.0, 0.0, 1.0, 3.0],
|
|
110
|
+
... [0.0, 0.0, 0.0, 1.0],
|
|
111
|
+
... ],
|
|
112
|
+
... )
|
|
113
|
+
True
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
T: The homogeneous transformation matrix, shape (4, 4).
|
|
78
117
|
"""
|
|
79
118
|
return np.array(
|
|
80
119
|
[
|
|
@@ -88,10 +127,10 @@ def translate3d(tx: float, ty: float, tz: float) -> npt.NDArray[np.float32]:
|
|
|
88
127
|
|
|
89
128
|
|
|
90
129
|
def rotate3d(n: npt.ArrayLike, theta: float) -> npt.NDArray[np.float32]:
|
|
91
|
-
r"""Get the 3D rotation
|
|
130
|
+
r"""Get the 3D rotation transformation matrix.
|
|
92
131
|
|
|
93
|
-
Rotate v with axis n by an angle theta according to the right hand rule,
|
|
94
|
-
|
|
132
|
+
Rotate v with axis n by an angle theta according to the right hand rule, follow
|
|
133
|
+
rodrigues' rotation formula.
|
|
95
134
|
|
|
96
135
|
.. math::
|
|
97
136
|
|
|
@@ -105,17 +144,12 @@ def rotate3d(n: npt.ArrayLike, theta: float) -> npt.NDArray[np.float32]:
|
|
|
105
144
|
-n_y & n_x & 0
|
|
106
145
|
\end{pmatrix}
|
|
107
146
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
Returns
|
|
116
|
-
-------
|
|
117
|
-
T : np.NDArray
|
|
118
|
-
The homogeneous transfomation matrix, shape (4, 4).
|
|
147
|
+
Args:
|
|
148
|
+
n: Rotation axis.
|
|
149
|
+
theta: Rotation angle in radians.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
T: The homogeneous transformation matrix, shape (4, 4).
|
|
119
153
|
"""
|
|
120
154
|
|
|
121
155
|
n = np.array(n)
|
|
@@ -138,20 +172,28 @@ def rotate3d(n: npt.ArrayLike, theta: float) -> npt.NDArray[np.float32]:
|
|
|
138
172
|
|
|
139
173
|
|
|
140
174
|
def rotate3d_x(theta: float) -> npt.NDArray[np.float32]:
|
|
141
|
-
"""Get the 3D rotation
|
|
175
|
+
"""Get the 3D rotation transformation matrix.
|
|
142
176
|
|
|
143
177
|
Rotate 3D vector `v` with `x`-axis by an angle theta according to the right
|
|
144
178
|
hand rule.
|
|
145
179
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
180
|
+
>>> np.allclose(
|
|
181
|
+
... rotate3d_x(np.pi / 2), # 90 degree rotation
|
|
182
|
+
... [
|
|
183
|
+
... [+1.0, +0.0, +0.0, +0.0],
|
|
184
|
+
... [+0.0, +0.0, -1.0, +0.0],
|
|
185
|
+
... [+0.0, +1.0, +0.0, +0.0],
|
|
186
|
+
... [+0.0, +0.0, +0.0, +1.0],
|
|
187
|
+
... ],
|
|
188
|
+
... )
|
|
189
|
+
True
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
theta: float
|
|
193
|
+
Rotation angle in radians.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
T: The homogeneous transformation matrix, shape (4, 4).
|
|
155
197
|
"""
|
|
156
198
|
|
|
157
199
|
return np.array(
|
|
@@ -166,20 +208,27 @@ def rotate3d_x(theta: float) -> npt.NDArray[np.float32]:
|
|
|
166
208
|
|
|
167
209
|
|
|
168
210
|
def rotate3d_y(theta: float) -> npt.NDArray[np.float32]:
|
|
169
|
-
"""Get the 3D rotation
|
|
211
|
+
"""Get the 3D rotation transformation matrix.
|
|
170
212
|
|
|
171
213
|
Rotate 3D vector `v` with `y`-axis by an angle theta according to the right
|
|
172
214
|
hand rule.
|
|
173
215
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
216
|
+
>>> np.allclose(
|
|
217
|
+
... rotate3d_y(np.pi / 2), # 90 degree rotation
|
|
218
|
+
... [
|
|
219
|
+
... [+0.0, +0.0, +1.0, +0.0],
|
|
220
|
+
... [+0.0, +1.0, +0.0, +0.0],
|
|
221
|
+
... [-1.0, +0.0, +0.0, +0.0],
|
|
222
|
+
... [+0.0, +0.0, +0.0, +1.0],
|
|
223
|
+
... ],
|
|
224
|
+
... )
|
|
225
|
+
True
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
theta: Rotation angle in radians.
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
T: The homogeneous transformation matrix, shape (4, 4).
|
|
183
232
|
"""
|
|
184
233
|
return np.array(
|
|
185
234
|
[
|
|
@@ -193,20 +242,29 @@ def rotate3d_y(theta: float) -> npt.NDArray[np.float32]:
|
|
|
193
242
|
|
|
194
243
|
|
|
195
244
|
def rotate3d_z(theta: float) -> npt.NDArray[np.float32]:
|
|
196
|
-
"""Get the 3D rotation
|
|
197
|
-
|
|
198
|
-
Rotate 3D vector `v` with `z`-axis by an angle theta according to the right
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
245
|
+
"""Get the 3D rotation transformation matrix.
|
|
246
|
+
|
|
247
|
+
Rotate 3D vector `v` with `z`-axis by an angle theta according to the right hand
|
|
248
|
+
rule.
|
|
249
|
+
|
|
250
|
+
>>> np.allclose(
|
|
251
|
+
... rotate3d_z(np.pi / 2), # 90 degree rotation
|
|
252
|
+
... [
|
|
253
|
+
... [+0.0, -1.0, +0.0, +0.0],
|
|
254
|
+
... [+1.0, +0.0, +0.0, +0.0],
|
|
255
|
+
... [+0.0, +0.0, +1.0, +0.0],
|
|
256
|
+
... [+0.0, +0.0, +0.0, +1.0],
|
|
257
|
+
... ],
|
|
258
|
+
... )
|
|
259
|
+
True
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
theta: float
|
|
263
|
+
Rotation angle in radians.
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
T: np.NDArray
|
|
267
|
+
The homogeneous transformation matrix, shape (4, 4).
|
|
210
268
|
"""
|
|
211
269
|
return np.array(
|
|
212
270
|
[
|
|
@@ -222,17 +280,20 @@ def rotate3d_z(theta: float) -> npt.NDArray[np.float32]:
|
|
|
222
280
|
def to_homogeneous(xyz: npt.ArrayLike, w: float) -> npt.NDArray[np.float32]:
|
|
223
281
|
"""Fill xyz to homogeneous coordinates.
|
|
224
282
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
283
|
+
>>> np.allclose(to_homogeneous([1, 2, 3], 1), [1.0, 2.0, 3.0, 1.0])
|
|
284
|
+
True
|
|
285
|
+
>>> np.allclose(
|
|
286
|
+
... to_homogeneous([[1, 2, 3], [4, 5, 6]], 0),
|
|
287
|
+
... [[1.0, 2.0, 3.0, 0.0], [4.0, 5.0, 6.0, 0.0]],
|
|
288
|
+
... )
|
|
289
|
+
True
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
xyz: Coordinate of shape (..., 3)
|
|
293
|
+
w: w of homogeneous coordinate, 1 for dot, 0 for vector.
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
xyz4: Array of shape (..., 4)
|
|
236
297
|
"""
|
|
237
298
|
xyz = np.array(xyz)
|
|
238
299
|
if xyz.ndim == 1:
|
|
@@ -247,17 +308,12 @@ def to_homogeneous(xyz: npt.ArrayLike, w: float) -> npt.NDArray[np.float32]:
|
|
|
247
308
|
def _to_homogeneous(xyz: npt.NDArray, w: float) -> npt.NDArray[np.float32]:
|
|
248
309
|
"""Fill xyz to homogeneous coordinates.
|
|
249
310
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
Returns
|
|
258
|
-
-------
|
|
259
|
-
xyz4 : npt.NDArray[np.float32]
|
|
260
|
-
Array of shape (N, 4)
|
|
311
|
+
Args:
|
|
312
|
+
xyz: Coordinate of shape (N, 3)
|
|
313
|
+
w: w of homogeneous coordinate, 1 for dot, 0 for vector.
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
xyz4: Array of shape (N, 4)
|
|
261
317
|
"""
|
|
262
318
|
if xyz.shape[1] == 4:
|
|
263
319
|
return xyz
|
|
@@ -273,14 +329,10 @@ def model_view_transformation(
|
|
|
273
329
|
) -> npt.NDArray[np.float32]:
|
|
274
330
|
r"""Play model/view transformation.
|
|
275
331
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
Camera
|
|
280
|
-
look_at: Tuple[float, float, float]
|
|
281
|
-
Camera look-at \vec{g}.
|
|
282
|
-
up: Tuple[float, float, float]
|
|
283
|
-
Camera up direction \vec{t}.
|
|
332
|
+
Args:
|
|
333
|
+
position: Camera position \vec{e}.
|
|
334
|
+
look_at: Camera look-at \vec{g}.
|
|
335
|
+
up: Camera up direction \vec{t}.
|
|
284
336
|
"""
|
|
285
337
|
|
|
286
338
|
e = np.array(position, dtype=np.float32)
|
|
@@ -301,7 +353,19 @@ def model_view_transformation(
|
|
|
301
353
|
|
|
302
354
|
|
|
303
355
|
def orthographic_projection_simple() -> npt.NDArray[np.float32]:
|
|
304
|
-
"""Simple orthographic projection by drop z-axis
|
|
356
|
+
"""Simple orthographic projection by drop z-axis
|
|
357
|
+
|
|
358
|
+
>>> np.allclose(
|
|
359
|
+
... orthographic_projection_simple(),
|
|
360
|
+
... [
|
|
361
|
+
... [1.0, 0.0, 0.0, 0.0],
|
|
362
|
+
... [0.0, 1.0, 0.0, 0.0],
|
|
363
|
+
... [0.0, 0.0, 0.0, 0.0],
|
|
364
|
+
... [0.0, 0.0, 0.0, 0.0],
|
|
365
|
+
... ],
|
|
366
|
+
... )
|
|
367
|
+
True
|
|
368
|
+
"""
|
|
305
369
|
return np.array(
|
|
306
370
|
[
|
|
307
371
|
[1, 0, 0, 0],
|