amaazetools 0.1.2__tar.gz → 0.1.4__tar.gz
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.
- {amaazetools-0.1.2/amaazetools.egg-info → amaazetools-0.1.4}/PKG-INFO +2 -2
- {amaazetools-0.1.2 → amaazetools-0.1.4}/README.md +1 -1
- {amaazetools-0.1.2 → amaazetools-0.1.4}/amaazetools/trimesh.py +159 -123
- {amaazetools-0.1.2 → amaazetools-0.1.4/amaazetools.egg-info}/PKG-INFO +2 -2
- {amaazetools-0.1.2 → amaazetools-0.1.4}/pyproject.toml +1 -1
- {amaazetools-0.1.2 → amaazetools-0.1.4}/src/cextensions.c +2 -0
- {amaazetools-0.1.2 → amaazetools-0.1.4}/LICENSE +0 -0
- {amaazetools-0.1.2 → amaazetools-0.1.4}/MANIFEST.in +0 -0
- {amaazetools-0.1.2 → amaazetools-0.1.4}/amaazetools/__init__.py +0 -0
- {amaazetools-0.1.2 → amaazetools-0.1.4}/amaazetools/dicom.py +0 -0
- {amaazetools-0.1.2 → amaazetools-0.1.4}/amaazetools/edge_detection.py +0 -0
- {amaazetools-0.1.2 → amaazetools-0.1.4}/amaazetools/mesh_segmentation.py +0 -0
- {amaazetools-0.1.2 → amaazetools-0.1.4}/amaazetools/svi.py +0 -0
- {amaazetools-0.1.2 → amaazetools-0.1.4}/amaazetools.egg-info/SOURCES.txt +0 -0
- {amaazetools-0.1.2 → amaazetools-0.1.4}/amaazetools.egg-info/dependency_links.txt +0 -0
- {amaazetools-0.1.2 → amaazetools-0.1.4}/amaazetools.egg-info/requires.txt +0 -0
- {amaazetools-0.1.2 → amaazetools-0.1.4}/amaazetools.egg-info/top_level.txt +0 -0
- {amaazetools-0.1.2 → amaazetools-0.1.4}/setup.cfg +0 -0
- {amaazetools-0.1.2 → amaazetools-0.1.4}/setup.py +0 -0
- {amaazetools-0.1.2 → amaazetools-0.1.4}/src/memory_allocation.c +0 -0
- {amaazetools-0.1.2 → amaazetools-0.1.4}/src/memory_allocation.h +0 -0
- {amaazetools-0.1.2 → amaazetools-0.1.4}/src/mesh_operations.c +0 -0
- {amaazetools-0.1.2 → amaazetools-0.1.4}/src/mesh_operations.h +0 -0
- {amaazetools-0.1.2 → amaazetools-0.1.4}/src/svi_computations.c +0 -0
- {amaazetools-0.1.2 → amaazetools-0.1.4}/src/svi_computations.h +0 -0
- {amaazetools-0.1.2 → amaazetools-0.1.4}/src/vector_operations.h +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: amaazetools
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Python package for mesh processing tools developed by AMAAZE
|
|
5
5
|
Author-email: Jeff Calder <jwcalder@umn.edu>
|
|
6
6
|
License: MIT
|
|
@@ -50,7 +50,7 @@ Email <jwcalder@umn.edu> with any questions or comments.
|
|
|
50
50
|
|
|
51
51
|
## Contributors
|
|
52
52
|
|
|
53
|
-
Several people have contributed to the development of this software:
|
|
53
|
+
Several people have contributed to the development of this software:
|
|
54
54
|
|
|
55
55
|
1. David Floeder
|
|
56
56
|
2. Riley O'Neill
|
|
@@ -26,7 +26,7 @@ Email <jwcalder@umn.edu> with any questions or comments.
|
|
|
26
26
|
|
|
27
27
|
## Contributors
|
|
28
28
|
|
|
29
|
-
Several people have contributed to the development of this software:
|
|
29
|
+
Several people have contributed to the development of this software:
|
|
30
30
|
|
|
31
31
|
1. David Floeder
|
|
32
32
|
2. Riley O'Neill
|
|
@@ -10,6 +10,7 @@ from plyfile import PlyData, PlyElement
|
|
|
10
10
|
import scipy.sparse as sparse
|
|
11
11
|
import scipy.spatial as spatial
|
|
12
12
|
from skimage import measure
|
|
13
|
+
from skimage.color import convert_colorspace
|
|
13
14
|
from sklearn.neighbors import NearestNeighbors
|
|
14
15
|
from . import svi
|
|
15
16
|
from . import edge_detection
|
|
@@ -18,6 +19,58 @@ import urllib.request as url
|
|
|
18
19
|
|
|
19
20
|
#Non-Class Specific Functions
|
|
20
21
|
|
|
22
|
+
|
|
23
|
+
def marching_cubes(volume,level=None,spacing=(1,1,1)):
|
|
24
|
+
""" SK-Image's marching cubes does not return a clean triangulations -
|
|
25
|
+
often has replicate points, self-triangles, etc. This fixes that.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
volume : (l,w,h) float array
|
|
30
|
+
A 3-D grid discretization of the function to be surfaced.
|
|
31
|
+
level : float
|
|
32
|
+
isolevel to extract surface at.
|
|
33
|
+
spacing : (3) float
|
|
34
|
+
denotes the dimensions of each voxel in the volume.
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
Returns
|
|
38
|
+
-------
|
|
39
|
+
p : (n,3) float
|
|
40
|
+
points of triangulation
|
|
41
|
+
t : (m,3) int
|
|
42
|
+
triangles of triangulation
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
p,t,n,val = measure.marching_cubes(volume,level=level,spacing=spacing)
|
|
46
|
+
|
|
47
|
+
#sometimes marching cubes produces... artifacts... so here's a very basic intro to mesh cleaning!
|
|
48
|
+
#first: eliminate repeated points:
|
|
49
|
+
p,index,inv = np.unique(p,axis=0, return_index=True,return_inverse=True)
|
|
50
|
+
|
|
51
|
+
#next: rid triangulation of non-triangles:
|
|
52
|
+
t = inv[t]
|
|
53
|
+
badt = (t[:,0]==t[:,1])+(t[:,1]==t[:,2])+(t[:,2]==t[:,0])
|
|
54
|
+
t = t[badt ==False,:]
|
|
55
|
+
|
|
56
|
+
#remove unreferenced points, if any
|
|
57
|
+
ind = -1*np.ones(p.shape[0],int)
|
|
58
|
+
uni = np.unique(t.flatten()) #these are already sorted for numpy
|
|
59
|
+
ind[uni] = np.arange(uni.shape[0])
|
|
60
|
+
p = p[ind>-1,:]
|
|
61
|
+
t = ind[t]
|
|
62
|
+
|
|
63
|
+
#finally, check right hand rule... this works for ~convex objects, anyway...
|
|
64
|
+
#m = tm.mesh(p,t)
|
|
65
|
+
|
|
66
|
+
#tc = m.face_centers()
|
|
67
|
+
#tn = m.face_normals()
|
|
68
|
+
|
|
69
|
+
#cent = m.points.mean(0)
|
|
70
|
+
#fliporder = np.sum(tn*(tc-cent),1)<0
|
|
71
|
+
#t[fliporder,1:3] = t[fliporder,2:0:-1]
|
|
72
|
+
return p,t
|
|
73
|
+
|
|
21
74
|
def withiness(x):
|
|
22
75
|
""" Computes withiness (how well 1-D data clusters into two groups).
|
|
23
76
|
|
|
@@ -25,7 +78,7 @@ def withiness(x):
|
|
|
25
78
|
----------
|
|
26
79
|
x : (n,1) float array
|
|
27
80
|
A 1-D collection of data.
|
|
28
|
-
|
|
81
|
+
|
|
29
82
|
Returns
|
|
30
83
|
-------
|
|
31
84
|
w : float
|
|
@@ -56,7 +109,7 @@ def pca(P):
|
|
|
56
109
|
----------
|
|
57
110
|
P : (n,d) float array
|
|
58
111
|
A point cloud.
|
|
59
|
-
|
|
112
|
+
|
|
60
113
|
Returns
|
|
61
114
|
-------
|
|
62
115
|
vals : (d,) float arrayy
|
|
@@ -64,13 +117,13 @@ def pca(P):
|
|
|
64
117
|
vecs : (d,d) float array
|
|
65
118
|
The principal component vectors.
|
|
66
119
|
"""
|
|
67
|
-
|
|
120
|
+
|
|
68
121
|
P = P - np.mean(P,axis=0)
|
|
69
122
|
vals,vecs = np.linalg.eig(P.T@P)
|
|
70
123
|
idx = np.argsort(-vals)
|
|
71
124
|
|
|
72
125
|
return vals[idx],vecs[:,idx]
|
|
73
|
-
|
|
126
|
+
|
|
74
127
|
def weighted_pca(P,W):
|
|
75
128
|
""" Computes weighted principal component analysis (PCA) on a point cloud P.
|
|
76
129
|
|
|
@@ -80,7 +133,7 @@ def weighted_pca(P,W):
|
|
|
80
133
|
A point cloud.
|
|
81
134
|
W : (n,) float array
|
|
82
135
|
An array containing the weights of the points.
|
|
83
|
-
|
|
136
|
+
|
|
84
137
|
Returns
|
|
85
138
|
-------
|
|
86
139
|
vals : (d,) float array
|
|
@@ -107,7 +160,7 @@ def power_method(A,tol=1e-12):
|
|
|
107
160
|
A square matrix that one wishes to find the smallest (in absolute value) eigenvalue and corresponding eigenvector of.
|
|
108
161
|
tol : float, default is 1e-12
|
|
109
162
|
The desired tolerance threshold after which to stop iteration.
|
|
110
|
-
|
|
163
|
+
|
|
111
164
|
Parameters
|
|
112
165
|
----------
|
|
113
166
|
l : float
|
|
@@ -137,7 +190,7 @@ def pca_smallest_eig_powermethod(X,center=True):
|
|
|
137
190
|
A point cloud.
|
|
138
191
|
center : boolean, default is True
|
|
139
192
|
Data is centered if True.
|
|
140
|
-
|
|
193
|
+
|
|
141
194
|
Returns
|
|
142
195
|
-------
|
|
143
196
|
A float array of size (3,) containing the last principal component vector.
|
|
@@ -162,7 +215,7 @@ def pca_smallest_eig(X,center=True):
|
|
|
162
215
|
A point cloud.
|
|
163
216
|
center : boolean, default is True
|
|
164
217
|
Data is centered if True.
|
|
165
|
-
|
|
218
|
+
|
|
166
219
|
Returns
|
|
167
220
|
-------
|
|
168
221
|
A float array of size (3,) containing the last principal component vector.
|
|
@@ -187,7 +240,7 @@ def read_ply(fname):
|
|
|
187
240
|
----------
|
|
188
241
|
fname: str
|
|
189
242
|
Name of the file to read from.
|
|
190
|
-
|
|
243
|
+
|
|
191
244
|
Returns
|
|
192
245
|
-------
|
|
193
246
|
P : (num_verts,3) float array
|
|
@@ -221,7 +274,7 @@ def load_ply(path):
|
|
|
221
274
|
----------
|
|
222
275
|
path : str
|
|
223
276
|
URL or file path at which to access .ply file.
|
|
224
|
-
|
|
277
|
+
|
|
225
278
|
Returns
|
|
226
279
|
-------
|
|
227
280
|
A mesh object generated from a .ply file found at the file path location.
|
|
@@ -247,12 +300,12 @@ def synth_mesh(angle, num_pts):
|
|
|
247
300
|
|
|
248
301
|
Parameters
|
|
249
302
|
----------
|
|
250
|
-
angle: float
|
|
303
|
+
angle: float
|
|
251
304
|
Intersection angle.
|
|
252
305
|
num_pts : int
|
|
253
306
|
Number of vertices in the mesh.
|
|
254
|
-
|
|
255
|
-
|
|
307
|
+
|
|
308
|
+
|
|
256
309
|
Returns
|
|
257
310
|
-------
|
|
258
311
|
A mesh object.
|
|
@@ -279,7 +332,7 @@ def synth_mesh(angle, num_pts):
|
|
|
279
332
|
verts[:,1] *= np.tan(angle*np.pi/180/2)
|
|
280
333
|
|
|
281
334
|
#Create mesh and flip normals
|
|
282
|
-
m = mesh(verts,faces)
|
|
335
|
+
m = mesh(verts,faces)
|
|
283
336
|
m.flip_normals()
|
|
284
337
|
|
|
285
338
|
return m
|
|
@@ -386,7 +439,7 @@ class mesh:
|
|
|
386
439
|
#so F can be used to interplate from triangles to vertices
|
|
387
440
|
def tri_vert_adj(self,normalize=False):
|
|
388
441
|
""" Computes a sparse vertex-triangle adjacency matrix.
|
|
389
|
-
|
|
442
|
+
|
|
390
443
|
Parameters
|
|
391
444
|
----------
|
|
392
445
|
normalize : boolean, default is False
|
|
@@ -417,7 +470,7 @@ class mesh:
|
|
|
417
470
|
#Returns unit normal vectors to vertices (averaging adjacent faces and normalizing)
|
|
418
471
|
def vertex_normals(self):
|
|
419
472
|
""" Computes normal vectors to vertices.
|
|
420
|
-
|
|
473
|
+
|
|
421
474
|
Returns
|
|
422
475
|
-------
|
|
423
476
|
A (num_verts,3) float array containing the vertex normal vectors.
|
|
@@ -432,16 +485,16 @@ class mesh:
|
|
|
432
485
|
norms[norms==0] = 1
|
|
433
486
|
|
|
434
487
|
return vn/norms[:,np.newaxis]
|
|
435
|
-
|
|
488
|
+
|
|
436
489
|
#Returns unit normal vectors
|
|
437
490
|
def face_normals(self,normalize=True):
|
|
438
491
|
""" Computes normal vectors to triangles (faces).
|
|
439
|
-
|
|
492
|
+
|
|
440
493
|
Parameters
|
|
441
494
|
----------
|
|
442
495
|
normalize: boolean, default is True
|
|
443
496
|
Whether or not to normalize to unit vectors; if False, vector magnitude is twice the area of the corresponding triangle.
|
|
444
|
-
|
|
497
|
+
|
|
445
498
|
Returns
|
|
446
499
|
-------
|
|
447
500
|
N : (num_tri,3) float array
|
|
@@ -460,7 +513,7 @@ class mesh:
|
|
|
460
513
|
else:
|
|
461
514
|
self.norms = N
|
|
462
515
|
return N
|
|
463
|
-
|
|
516
|
+
|
|
464
517
|
def flip_normals(self):
|
|
465
518
|
""" Reverses the orientation of all normal vectors in the mesh
|
|
466
519
|
"""
|
|
@@ -470,7 +523,7 @@ class mesh:
|
|
|
470
523
|
#Areas of all triangles in mesh
|
|
471
524
|
def tri_areas(self):
|
|
472
525
|
""" Computes areas of all triangles in the mesh.
|
|
473
|
-
|
|
526
|
+
|
|
474
527
|
Returns
|
|
475
528
|
-------
|
|
476
529
|
A (num_tri,) float array containing the areas of each triangle (face).
|
|
@@ -483,18 +536,18 @@ class mesh:
|
|
|
483
536
|
#Surface area of mesh
|
|
484
537
|
def surf_area(self):
|
|
485
538
|
""" Computes surface area of the mesh.
|
|
486
|
-
|
|
539
|
+
|
|
487
540
|
Returns
|
|
488
541
|
-------
|
|
489
542
|
The surface area of the entire mesh as a float.
|
|
490
543
|
"""
|
|
491
544
|
|
|
492
545
|
return np.sum(self.tri_areas())
|
|
493
|
-
|
|
546
|
+
|
|
494
547
|
#Centers of each face
|
|
495
548
|
def face_centers(self):
|
|
496
549
|
""" Computes coordinates of the center of each triangle (face).
|
|
497
|
-
|
|
550
|
+
|
|
498
551
|
Returns
|
|
499
552
|
-------
|
|
500
553
|
A (num_tri,3) float array containing the coordinates of the face centers.
|
|
@@ -507,11 +560,11 @@ class mesh:
|
|
|
507
560
|
result = (P1 + P2 + P3)/3
|
|
508
561
|
self.centers = result
|
|
509
562
|
return result
|
|
510
|
-
|
|
563
|
+
|
|
511
564
|
#Volume enclosed by mesh
|
|
512
565
|
def volume(self):
|
|
513
566
|
""" Computes the volume of the mesh.
|
|
514
|
-
|
|
567
|
+
|
|
515
568
|
Returns
|
|
516
569
|
-------
|
|
517
570
|
The volume of the mesh as a float.
|
|
@@ -524,10 +577,10 @@ class mesh:
|
|
|
524
577
|
if self.norms is None:
|
|
525
578
|
self.face_normals(False)
|
|
526
579
|
return np.sum(X*self.norms)/6
|
|
527
|
-
|
|
580
|
+
|
|
528
581
|
def bbox(self):
|
|
529
582
|
""" Computes the bounding box of the mesh.
|
|
530
|
-
|
|
583
|
+
|
|
531
584
|
Returns
|
|
532
585
|
-------
|
|
533
586
|
A (3,) float array containing the dimensions of the bounding box.
|
|
@@ -547,10 +600,10 @@ class mesh:
|
|
|
547
600
|
|
|
548
601
|
Y = X@vecs
|
|
549
602
|
bb = np.max(Y,axis=0) - np.min(Y,axis=0)
|
|
550
|
-
|
|
603
|
+
|
|
551
604
|
return bb
|
|
552
|
-
|
|
553
|
-
|
|
605
|
+
|
|
606
|
+
|
|
554
607
|
#Plot triangulated surface
|
|
555
608
|
def plotsurf(self,C=None):
|
|
556
609
|
""" Plots the mesh as a surface using mayavi.
|
|
@@ -559,7 +612,7 @@ class mesh:
|
|
|
559
612
|
----------
|
|
560
613
|
C : (num_verts,3) int array, default is None
|
|
561
614
|
An optional per-vertex labeling scheme to use.
|
|
562
|
-
|
|
615
|
+
|
|
563
616
|
Returns
|
|
564
617
|
-------
|
|
565
618
|
A visualization of the mesh.
|
|
@@ -578,7 +631,7 @@ class mesh:
|
|
|
578
631
|
----------
|
|
579
632
|
C : (num_verts,3) int array, default is -1
|
|
580
633
|
An optional per-vertex labeling scheme to use.
|
|
581
|
-
|
|
634
|
+
|
|
582
635
|
Returns
|
|
583
636
|
-------
|
|
584
637
|
mesh : amaazetools.trimesh.mesh object
|
|
@@ -588,7 +641,7 @@ class mesh:
|
|
|
588
641
|
from mayavi import mlab
|
|
589
642
|
if C.any == -1: #if no C given
|
|
590
643
|
C = np.ones((len(x),1))
|
|
591
|
-
|
|
644
|
+
|
|
592
645
|
n = len(np.unique(C))
|
|
593
646
|
C = C.astype(int)
|
|
594
647
|
if n>20:
|
|
@@ -597,17 +650,22 @@ class mesh:
|
|
|
597
650
|
col = (np.arange(1,n+1)) / n
|
|
598
651
|
colors = col[C-1]
|
|
599
652
|
mesh = mlab.triangular_mesh(self.points[:,0],self.points[:,1],self.points[:,2],self.triangles,scalars=colors)
|
|
600
|
-
|
|
653
|
+
|
|
601
654
|
return mesh
|
|
602
|
-
|
|
655
|
+
|
|
603
656
|
#Write a ply file
|
|
604
|
-
def to_ply(self,fname):
|
|
657
|
+
def to_ply(self,fname,c=None):
|
|
605
658
|
""" Writes the mesh to a .ply file.
|
|
606
659
|
|
|
607
660
|
Parameters
|
|
608
661
|
----------
|
|
609
662
|
fname : str
|
|
610
663
|
The name of the .ply file to write the mesh to.
|
|
664
|
+
c : numpy array
|
|
665
|
+
Color array. If provided, then color is added to ply file.
|
|
666
|
+
If array is num_vert x 3, it is interprted as RGB colors
|
|
667
|
+
in the range 0,...,255. If the array is one dimensional
|
|
668
|
+
of length num_vert, then the values are interpreted as hues.
|
|
611
669
|
"""
|
|
612
670
|
|
|
613
671
|
f = open(fname,"w")
|
|
@@ -619,6 +677,11 @@ class mesh:
|
|
|
619
677
|
f.write('property double x\n')
|
|
620
678
|
f.write('property double y\n')
|
|
621
679
|
f.write('property double z\n')
|
|
680
|
+
#Write color header if colors are provided
|
|
681
|
+
if c is not None:
|
|
682
|
+
f.write('property uchar red\n')
|
|
683
|
+
f.write('property uchar green\n')
|
|
684
|
+
f.write('property uchar blue\n')
|
|
622
685
|
f.write('element face %u\n'%self.num_tri())
|
|
623
686
|
f.write('property list int int vertex_indices\n')
|
|
624
687
|
f.write('end_header\n')
|
|
@@ -626,8 +689,23 @@ class mesh:
|
|
|
626
689
|
|
|
627
690
|
f = open(fname,"ab")
|
|
628
691
|
|
|
629
|
-
#
|
|
630
|
-
|
|
692
|
+
#If no colors are provided
|
|
693
|
+
if c is None:
|
|
694
|
+
#write vertices
|
|
695
|
+
f.write(self.points.astype('float64').tobytes())
|
|
696
|
+
#If colors are provided
|
|
697
|
+
else:
|
|
698
|
+
#If scalars provided, then convert from hue to rgb
|
|
699
|
+
if c.ndim == 1:
|
|
700
|
+
c = c - np.min(c)
|
|
701
|
+
c = c/np.max(c)
|
|
702
|
+
arr = np.vstack((c,np.ones_like(c),np.ones_like(c))).T
|
|
703
|
+
c = 255*convert_colorspace(arr,'HSV','RGB')
|
|
704
|
+
|
|
705
|
+
#write vertices
|
|
706
|
+
for i in range(self.num_verts()):
|
|
707
|
+
f.write(self.points[i,:].astype('float64').tobytes())
|
|
708
|
+
f.write(c[i,:].astype('uint8').tobytes())
|
|
631
709
|
|
|
632
710
|
#write faces
|
|
633
711
|
T = np.hstack((np.ones((self.num_tri(),1))*3,self.triangles)).astype(int)
|
|
@@ -635,49 +713,7 @@ class mesh:
|
|
|
635
713
|
|
|
636
714
|
#close file
|
|
637
715
|
f.close()
|
|
638
|
-
|
|
639
|
-
#Write a ply file
|
|
640
|
-
def write_color_ply(self,color,fname):
|
|
641
|
-
""" Writes the colored mesh to a .ply file.
|
|
642
|
-
|
|
643
|
-
Parameters
|
|
644
|
-
----------
|
|
645
|
-
color : (num,verts,3) float array
|
|
646
|
-
An array of color data for each point.
|
|
647
|
-
fname : str
|
|
648
|
-
The name of the .ply file to write the colored mesh to.
|
|
649
|
-
"""
|
|
650
|
-
|
|
651
|
-
f = open(fname,"w")
|
|
652
|
-
|
|
653
|
-
#Write header
|
|
654
|
-
f.write('ply\n')
|
|
655
|
-
f.write('format binary_little_endian 1.0\n')
|
|
656
|
-
f.write('element vertex %u\n'%self.num_verts())
|
|
657
|
-
f.write('property double x\n')
|
|
658
|
-
f.write('property double y\n')
|
|
659
|
-
f.write('property double z\n')
|
|
660
|
-
f.write('property uchar red\n')
|
|
661
|
-
f.write('property uchar green\n')
|
|
662
|
-
f.write('property uchar blue\n')
|
|
663
|
-
f.write('element face %u\n'%self.num_tri())
|
|
664
|
-
f.write('property list int int vertex_indices\n')
|
|
665
|
-
f.write('end_header\n')
|
|
666
|
-
f.close()
|
|
667
716
|
|
|
668
|
-
f = open(fname,"ab")
|
|
669
|
-
|
|
670
|
-
#write vertices
|
|
671
|
-
for i in range(self.num_verts()):
|
|
672
|
-
f.write(P[i,:].astype('float64').tobytes())
|
|
673
|
-
f.write(color[i,:].astype('uint8').tobytes())
|
|
674
|
-
|
|
675
|
-
#write faces
|
|
676
|
-
T = np.hstack((np.ones((self.num_tri(),1))*3,T)).astype(int)
|
|
677
|
-
f.write(T.astype('int32').tobytes())
|
|
678
|
-
|
|
679
|
-
#close file
|
|
680
|
-
f.close()
|
|
681
717
|
|
|
682
718
|
def to_gif(self,fname,color = [],duration=7,fps=20,size=750,histeq = True):
|
|
683
719
|
""" Writes rotating gif
|
|
@@ -697,21 +733,21 @@ class mesh:
|
|
|
697
733
|
histeq : boolean, default is True
|
|
698
734
|
Performs histogram equalization on scalar color array; else should normalize prior to input.
|
|
699
735
|
"""
|
|
700
|
-
|
|
736
|
+
|
|
701
737
|
from skimage import exposure
|
|
702
738
|
from mayavi import mlab
|
|
703
739
|
import moviepy.editor as mpy
|
|
704
740
|
from pyface.api import GUI
|
|
705
|
-
|
|
741
|
+
|
|
706
742
|
#Make copy of points
|
|
707
743
|
X = self.points.copy()
|
|
708
|
-
|
|
744
|
+
|
|
709
745
|
if np.shape(color)[0] == np.shape(X)[0]: #scalars for plot
|
|
710
746
|
opt = 2
|
|
711
747
|
if histeq:
|
|
712
748
|
color = color - np.amin(color)
|
|
713
749
|
color = 1-exposure.equalize_hist(color/np.max(color),nbins=1000)
|
|
714
|
-
|
|
750
|
+
|
|
715
751
|
if np.shape(np.shape(color))[0]>1: #handle input
|
|
716
752
|
color = color[:,0]
|
|
717
753
|
elif max(np.shape(color)) == 3: #single rgb color
|
|
@@ -719,7 +755,7 @@ class mesh:
|
|
|
719
755
|
else : #not input - default to single color
|
|
720
756
|
color = (0.7,0.7,0.7)
|
|
721
757
|
opt = 1
|
|
722
|
-
|
|
758
|
+
|
|
723
759
|
#PCA
|
|
724
760
|
Mean = np.mean(X,axis=0)
|
|
725
761
|
cov_matrix = (X-Mean).T@(X-Mean)
|
|
@@ -754,14 +790,14 @@ class mesh:
|
|
|
754
790
|
|
|
755
791
|
def svi(self,r,ID=None):
|
|
756
792
|
""" Computes spherical volume invariant.
|
|
757
|
-
|
|
793
|
+
|
|
758
794
|
Parameters
|
|
759
795
|
----------
|
|
760
796
|
r : (k,1) float array
|
|
761
797
|
List of radii to use.
|
|
762
798
|
ID : (n,1) boolean array, default is None
|
|
763
|
-
Spherical volume is only computed at points with True indices.
|
|
764
|
-
|
|
799
|
+
Spherical volume is only computed at points with True indices.
|
|
800
|
+
|
|
765
801
|
Returns
|
|
766
802
|
-------
|
|
767
803
|
S : (n,1) float array
|
|
@@ -769,7 +805,7 @@ class mesh:
|
|
|
769
805
|
G : (n,1) float array
|
|
770
806
|
The gamma values corresponding to each point.
|
|
771
807
|
"""
|
|
772
|
-
|
|
808
|
+
|
|
773
809
|
return svi.svi(self.points,self.triangles,r,ID=ID)
|
|
774
810
|
|
|
775
811
|
def svipca(self,r):
|
|
@@ -789,7 +825,7 @@ class mesh:
|
|
|
789
825
|
K2 : (n,1) float array
|
|
790
826
|
The second principle curvature for each point.
|
|
791
827
|
V1 : (n,3) float array
|
|
792
|
-
The first principal direction for each point.
|
|
828
|
+
The first principal direction for each point.
|
|
793
829
|
V2 : (n,3) float array
|
|
794
830
|
The second principal direction for each point.
|
|
795
831
|
V3 : (n,3) float array
|
|
@@ -800,7 +836,7 @@ class mesh:
|
|
|
800
836
|
|
|
801
837
|
def edge_graph_detect(self,**kwargs):
|
|
802
838
|
""" Detects edges using SVIPCA and principal direction metric.
|
|
803
|
-
|
|
839
|
+
|
|
804
840
|
Parameters
|
|
805
841
|
----------
|
|
806
842
|
M : amaazetools.trimesh.mesh object
|
|
@@ -828,7 +864,7 @@ class mesh:
|
|
|
828
864
|
Edges : (n,1) boolean array
|
|
829
865
|
A true value corresponds to that index being an edge point.
|
|
830
866
|
"""
|
|
831
|
-
|
|
867
|
+
|
|
832
868
|
return edge_detection.edge_graph_detect(self,**kwargs)
|
|
833
869
|
|
|
834
870
|
def graph_setup(self,n,r,p,seed=None):
|
|
@@ -844,7 +880,7 @@ class mesh:
|
|
|
844
880
|
Weight matrix parameter.
|
|
845
881
|
seed : int, default is None
|
|
846
882
|
Optional seed for random number generator.
|
|
847
|
-
|
|
883
|
+
|
|
848
884
|
Returns
|
|
849
885
|
-------
|
|
850
886
|
poisson_W_matrix : (n,n) scipy.sparse.lil_matrix
|
|
@@ -865,7 +901,7 @@ class mesh:
|
|
|
865
901
|
|
|
866
902
|
v = self.vertex_normals()
|
|
867
903
|
N = self.num_verts()
|
|
868
|
-
|
|
904
|
+
|
|
869
905
|
#Random subsample
|
|
870
906
|
ss_idx = np.matrix(rng.choice(self.points.shape[0],n,replace=False))
|
|
871
907
|
y = np.squeeze(self.points[ss_idx,:])
|
|
@@ -875,7 +911,7 @@ class mesh:
|
|
|
875
911
|
nn_idx = xTree.query_ball_point(y, r)
|
|
876
912
|
yTree = spatial.cKDTree(y)
|
|
877
913
|
nodes_idx = yTree.query_ball_point(y, r)
|
|
878
|
-
|
|
914
|
+
|
|
879
915
|
bn = np.zeros((n,3))
|
|
880
916
|
J = sparse.lil_matrix((N,n))
|
|
881
917
|
for i in range(n):
|
|
@@ -883,16 +919,16 @@ class mesh:
|
|
|
883
919
|
normal_diff = w[i] - vj
|
|
884
920
|
weights = np.exp(-8 * np.sum(np.square(normal_diff),1,keepdims=True))
|
|
885
921
|
bn[i] = np.sum(weights*vj,0) / np.sum(weights,0)
|
|
886
|
-
|
|
922
|
+
|
|
887
923
|
#Set ith row of J
|
|
888
924
|
normal_diff = bn[i]- vj
|
|
889
925
|
weights = np.exp(-8 * np.sum(np.square(normal_diff),1))#,keepdims=True))
|
|
890
926
|
J[nn_idx[i],i] = weights
|
|
891
|
-
|
|
927
|
+
|
|
892
928
|
#Normalize rows of J
|
|
893
929
|
RSM = sparse.spdiags((1 / np.sum(J,1)).ravel(),0,N,N)
|
|
894
930
|
J = RSM @ J
|
|
895
|
-
|
|
931
|
+
|
|
896
932
|
#Compute weight matrix W
|
|
897
933
|
W = sparse.lil_matrix((n,n))
|
|
898
934
|
for i in range(n):
|
|
@@ -900,7 +936,7 @@ class mesh:
|
|
|
900
936
|
normal_diff = bn[i] - nj
|
|
901
937
|
weights = np.exp(-32 * ((np.sqrt(np.sum(np.square(normal_diff),1)))/2)**p)
|
|
902
938
|
W[i,nodes_idx[i]] = weights
|
|
903
|
-
|
|
939
|
+
|
|
904
940
|
#Find nearest node to each vertex
|
|
905
941
|
nbrs = NearestNeighbors(n_neighbors=1, algorithm='ball_tree').fit(y)
|
|
906
942
|
instances, node_idx = nbrs.kneighbors(self.points)
|
|
@@ -908,8 +944,8 @@ class mesh:
|
|
|
908
944
|
self.poisson_W_matrix = W
|
|
909
945
|
self.poisson_J_matrix = J
|
|
910
946
|
self.poisson_node_idx = node_idx
|
|
911
|
-
|
|
912
|
-
return self.poisson_W_matrix, self.poisson_J_matrix, self.poisson_node_idx
|
|
947
|
+
|
|
948
|
+
return self.poisson_W_matrix, self.poisson_J_matrix, self.poisson_node_idx
|
|
913
949
|
|
|
914
950
|
def poisson_label(self,g,I,n=5000,r=0.5,p=1,s=None,graph_setup=False):
|
|
915
951
|
""" Performs poisson learning on the mesh.
|
|
@@ -930,13 +966,13 @@ class mesh:
|
|
|
930
966
|
Weights for fine-tuning Poisson learning.
|
|
931
967
|
graph_setup : boolean, default is False
|
|
932
968
|
Force graph construction if True.
|
|
933
|
-
|
|
969
|
+
|
|
934
970
|
Returns
|
|
935
971
|
-------
|
|
936
972
|
L : (num_verts,1) int array
|
|
937
973
|
Poisson labelling of each point in mesh.
|
|
938
974
|
"""
|
|
939
|
-
|
|
975
|
+
|
|
940
976
|
if graph_setup or (self.poisson_node_idx is None):
|
|
941
977
|
self.graph_setup(n,r,p)
|
|
942
978
|
|
|
@@ -955,7 +991,7 @@ class mesh:
|
|
|
955
991
|
self.poisson_labels = L
|
|
956
992
|
|
|
957
993
|
return L
|
|
958
|
-
|
|
994
|
+
|
|
959
995
|
def virtual_goniometer(self,point,r,k=7,SegParam=2,return_edge_points=False,
|
|
960
996
|
number_edge_points=None,return_euclidean_radius=False):
|
|
961
997
|
""" Runs a virtual goniometer to measure break angles.
|
|
@@ -977,7 +1013,7 @@ class mesh:
|
|
|
977
1013
|
Specifies how many edge points to return.
|
|
978
1014
|
return_euclidean_radius : boolean, default is False
|
|
979
1015
|
If True, returns Euclidean radius of patch.
|
|
980
|
-
|
|
1016
|
+
|
|
981
1017
|
Returns
|
|
982
1018
|
-------
|
|
983
1019
|
theta : float
|
|
@@ -1039,10 +1075,10 @@ def __virtual_goniometer__(P,N,SegParam=2,UsePCA=True,UsePower=False):
|
|
|
1039
1075
|
SegParam : float, default is 2
|
|
1040
1076
|
Segmentation parameter that encourages splitting patch in half as it increases in size.
|
|
1041
1077
|
UsePCA: boolean, default is True
|
|
1042
|
-
Uses PCA instead of averaged surface normals if True.
|
|
1078
|
+
Uses PCA instead of averaged surface normals if True.
|
|
1043
1079
|
UsePower : boolean, default is False
|
|
1044
1080
|
Uses the power method when doing PCA if True.
|
|
1045
|
-
|
|
1081
|
+
|
|
1046
1082
|
Returns
|
|
1047
1083
|
-------
|
|
1048
1084
|
theta : float
|
|
@@ -1107,11 +1143,11 @@ def __virtual_goniometer__(P,N,SegParam=2,UsePCA=True,UsePower=False):
|
|
|
1107
1143
|
n1 = n1/np.linalg.norm(n1)
|
|
1108
1144
|
n2 = np.average(N[C==2,:],axis=0)
|
|
1109
1145
|
n2 = n2/np.linalg.norm(n2)
|
|
1110
|
-
|
|
1146
|
+
|
|
1111
1147
|
#Angle between
|
|
1112
1148
|
theta = 180-np.arccos(np.dot(n1,n2))*180/np.pi
|
|
1113
1149
|
return theta,n1,n2,C
|
|
1114
|
-
|
|
1150
|
+
|
|
1115
1151
|
def conjgrad(A,b,x,T,tol):
|
|
1116
1152
|
""" Performs conjugate gradient descent.
|
|
1117
1153
|
|
|
@@ -1119,19 +1155,19 @@ def conjgrad(A,b,x,T,tol):
|
|
|
1119
1155
|
----------
|
|
1120
1156
|
A : matrix multiplying x
|
|
1121
1157
|
b : vector equal to product of A and x
|
|
1122
|
-
x : initial estimate for x
|
|
1158
|
+
x : initial estimate for x
|
|
1123
1159
|
T : int
|
|
1124
1160
|
Number of time steps allowed.
|
|
1125
1161
|
Tol : float
|
|
1126
1162
|
Desired convergence tolerance of result.
|
|
1127
|
-
|
|
1163
|
+
|
|
1128
1164
|
Returns
|
|
1129
1165
|
-------
|
|
1130
1166
|
x : calculated value for x
|
|
1131
1167
|
i : int
|
|
1132
1168
|
Number of iterations required for convergence.
|
|
1133
1169
|
"""
|
|
1134
|
-
|
|
1170
|
+
|
|
1135
1171
|
r = b - A@x
|
|
1136
1172
|
p = r
|
|
1137
1173
|
rsold = np.sum(r * r,0)
|
|
@@ -1158,13 +1194,13 @@ def poisson_learning(W,g,I):
|
|
|
1158
1194
|
Labels to assign to selected vertices.
|
|
1159
1195
|
I : (m,1) int array
|
|
1160
1196
|
Indices of user-selected vertices.
|
|
1161
|
-
|
|
1197
|
+
|
|
1162
1198
|
Returns
|
|
1163
1199
|
-------
|
|
1164
1200
|
u : (num_verts,1) int array
|
|
1165
1201
|
Poisson labels for each vertex in the mesh.
|
|
1166
1202
|
"""
|
|
1167
|
-
|
|
1203
|
+
|
|
1168
1204
|
k = len(np.unique(g))
|
|
1169
1205
|
n = W.shape[0]
|
|
1170
1206
|
m = len(I)
|
|
@@ -1176,19 +1212,19 @@ def poisson_learning(W,g,I):
|
|
|
1176
1212
|
F[I[i],g[i]] = 1
|
|
1177
1213
|
c = np.ones((1,n)) @ F / len(g)
|
|
1178
1214
|
F[I] -= c
|
|
1179
|
-
|
|
1215
|
+
|
|
1180
1216
|
deg = np.sum(W,1)
|
|
1181
1217
|
D = sparse.spdiags(deg.T,0,n,n)
|
|
1182
1218
|
L = D-W #Unnormalized graph laplacian matrix
|
|
1183
|
-
|
|
1219
|
+
|
|
1184
1220
|
#Preconditioning
|
|
1185
|
-
Dinv2 = sparse.spdiags(np.power(np.sum(W,1),-1/2).T,0,n,n)
|
|
1221
|
+
Dinv2 = sparse.spdiags(np.power(np.sum(W,1),-1/2).T,0,n,n)
|
|
1186
1222
|
Lnorm = Dinv2 @ L @ Dinv2
|
|
1187
1223
|
F = Dinv2 @ F
|
|
1188
|
-
|
|
1224
|
+
|
|
1189
1225
|
#Conjugate Gradient Solver
|
|
1190
1226
|
u,i = conjgrad(Lnorm,F,np.zeros((n,k)),1e5, np.sqrt(n)*1e-10)
|
|
1191
|
-
|
|
1227
|
+
|
|
1192
1228
|
#Undo preconditioning
|
|
1193
1229
|
u = Dinv2 @ u
|
|
1194
1230
|
return u
|
|
@@ -1200,18 +1236,18 @@ def canonical_labels(u):
|
|
|
1200
1236
|
----------
|
|
1201
1237
|
u : (num_verts,1) int array
|
|
1202
1238
|
A label vector.
|
|
1203
|
-
|
|
1239
|
+
|
|
1204
1240
|
Returns
|
|
1205
1241
|
-------
|
|
1206
1242
|
u : (num_verts,1) int array
|
|
1207
1243
|
A reodered label vector.
|
|
1208
1244
|
"""
|
|
1209
|
-
|
|
1245
|
+
|
|
1210
1246
|
n = len(u)
|
|
1211
1247
|
k = len(np.unique(u))
|
|
1212
1248
|
label_set = np.zeros((k,1))
|
|
1213
1249
|
label = 0
|
|
1214
|
-
|
|
1250
|
+
|
|
1215
1251
|
for i in range(n):
|
|
1216
1252
|
if u[i] > label:
|
|
1217
1253
|
label += 1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: amaazetools
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Python package for mesh processing tools developed by AMAAZE
|
|
5
5
|
Author-email: Jeff Calder <jwcalder@umn.edu>
|
|
6
6
|
License: MIT
|
|
@@ -50,7 +50,7 @@ Email <jwcalder@umn.edu> with any questions or comments.
|
|
|
50
50
|
|
|
51
51
|
## Contributors
|
|
52
52
|
|
|
53
|
-
Several people have contributed to the development of this software:
|
|
53
|
+
Several people have contributed to the development of this software:
|
|
54
54
|
|
|
55
55
|
1. David Floeder
|
|
56
56
|
2. Riley O'Neill
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|