forked from scikit-image/scikit-image
-
Notifications
You must be signed in to change notification settings - Fork 0
/
_marching_cubes_lewiner_cy.pyx
1443 lines (1229 loc) · 53.6 KB
/
_marching_cubes_lewiner_cy.pyx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#cython: cdivision=True
#cython: boundscheck=False
#cython: nonecheck=False
#cython: wraparound=False
"""
This is an implementation of the marching cubes algorithm proposed in:
Efficient implementation of Marching Cubes' cases with topological guarantees.
Thomas Lewiner, Helio Lopes, Antonio Wilson Vieira and Geovan Tavares.
Journal of Graphics Tools 8(2): pp. 1-15 (december 2003)
This algorithm has the advantage that it provides topologically correct
results, and the algorithms implementation is relatively simple. Most
of the magic is in the lookup tables, which are provided as open source.
Originally implemented in C++ by Thomas Lewiner in 2002, ported to Cython
by Almar Klein in 2012. Adapted for scikit-image in 2016.
"""
# Cython specific imports
import numpy as np
cimport numpy as np
import cython
np.import_array()
# Enable low level memory management
from libc.stdlib cimport malloc, free
# Define tiny winy number
cdef double FLT_EPSILON = np.spacing(1.0) #0.0000001
# Define abs function for doubles
cdef inline double dabs(double a): return a if a>=0 else -a
cdef inline int imin(int a, int b): return a if a<b else b
# todo: allow dynamic isovalue?
# todo: can we disable Cython from checking for zero division? Sometimes we know that it never happens!
def remove_degenerate_faces(vertices, faces, *arrays):
vertices_map0 = np.arange(len(vertices), dtype=np.int32)
vertices_map1 = vertices_map0.copy()
faces_ok = np.ones(len(faces), dtype=np.int32)
cdef float [:, :] vertices_ = vertices
cdef float [:] v1, v2, v3
cdef int [:, :] faces_ = faces
cdef int [:] vertices_map1_ = vertices_map1
cdef int [:] faces_ok_ = faces_ok
cdef int j, i1, i2, i3
# Iterate over all faces. When we encounter a degenerate triangle,
# we update the vertex map, i.e. we merge the corresponding vertices.
for j in range(faces_.shape[0]):
i1, i2, i3 = faces_[j][0], faces_[j][1], faces_[j][2]
v1, v2, v3 = vertices_[i1], vertices_[i2], vertices_[i3]
if v1[0] == v2[0] and v1[1] == v2[1] and v1[2] == v2[2]:
vertices_map1_[i1] = vertices_map1_[i2] = imin(vertices_map1_[i1], vertices_map1_[i2])
faces_ok_[j] = 0
if v1[0] == v3[0] and v1[1] == v3[1] and v1[2] == v3[2]:
vertices_map1_[i1] = vertices_map1_[i3] = imin(vertices_map1_[i1], vertices_map1_[i3])
faces_ok_[j] = 0
if v2[0] == v3[0] and v2[1] == v3[1] and v2[2] == v3[2]:
vertices_map1_[i2] = vertices_map1_[i3] = imin(vertices_map1_[i2], vertices_map1_[i3])
faces_ok_[j] = 0
# Create mask and mapping to new vertex indices
vertices_ok = vertices_map1 == vertices_map0
vertices_map2 = np.cumsum(vertices_ok) - 1
# Apply selection and mapping
faces2 = vertices_map2[vertices_map1[faces[faces_ok>0]]]
vertices2 = vertices[vertices_ok]
arrays2 = [arr[vertices_ok] for arr in arrays]
return (vertices2, faces2) + tuple(arrays2)
cdef class Cell:
""" Class to keep track of some stuff during the whole cube marching
procedure.
This "struct" keeps track of the current cell location, and the values
of corners of the cube. Gradients for the cube corners are calculated
when needed.
Additionally, it keeps track of the array of vertices, faces and normals.
Notes on vertices
-----------------
The vertices are stored in a C-array that is increased in size with
factors of two if needed. The same applies to the faces and normals.
Notes on faces
--------------
To keep track of the vertices already defined, this class maintains
two faceLayer arrays. faceLayer1 is of the current layer (z-value)
and faceLayer2 is of the next. Both face layers have 4 elements per
cell in that layer, 1 for each unique edge per cell (see
get_index_in_facelayer). These are initialized as -1, and set to the
index in the vertex array when a new vertex is created.
In summary, this allows us to keep track of the already created
vertices without keeping a very big array.
Notes on normals
----------------
The normal is simply defined as the gradient. Each time that a face is
created, we also add the gradient of that vertex position to the
normals array. The gradients are all calculated from the differences between
the 8 corners of the current cube, but because the final value of a normal
was contributed from multiple cells, the normals are quite accurate.
"""
# Reference to LUTS object
cdef LutProvider luts
# Location of cube
cdef int x
cdef int y
cdef int z
# Stepsize
cdef int step
# Values of cube corners (isovalue subtracted)
cdef double v0
cdef double v1
cdef double v2
cdef double v3
cdef double v4
cdef double v5
cdef double v6
cdef double v7
# Small arrays to store the above values in (allowing indexing)
# and also the gradient at these points
cdef double *vv
cdef double *vg
# Max value of the eight corners
cdef double vmax
# Vertex position of center of cube (only calculated if needed)
cdef double v12_x
cdef double v12_y
cdef double v12_z
# And corresponding gradient
cdef double v12_xg
cdef double v12_yg
cdef double v12_zg
cdef int v12_calculated # a boolean
# The index value, our magic 256 bit word
cdef int index
# Dimensions of the total volume
cdef int nx
cdef int ny
cdef int nz
# Arrays with face information
cdef int *faceLayer # The current facelayer (reference-copy of one of the below)
cdef int *faceLayer1 # The actual first face layer
cdef int *faceLayer2 # The actual second face layer
# Stuff to store the output vertices
cdef float *_vertices
cdef float *_normals
cdef float *_values
cdef int _vertexCount
cdef int _vertexMaxCount
# Stuff to store the output faces
cdef int *_faces
cdef int _faceCount
cdef int _faceMaxCount
def __init__(self, LutProvider luts, int nx, int ny, int nz):
self.luts = luts
self.nx, self.ny, self.nz = nx, ny, nz
# Allocate face layers
self.faceLayer1 = <int *>malloc(self.nx*self.ny*4 * sizeof(int))
self.faceLayer2 = <int *>malloc(self.nx*self.ny*4 * sizeof(int))
if (self.faceLayer1 is NULL or self.faceLayer2 is NULL or
self.vv is NULL or self.vg is NULL or self._vertices is NULL or
self._normals is NULL or self._values is NULL or
self._faces is NULL):
raise MemoryError()
cdef int i
for i in range(self.nx*self.ny*4):
self.faceLayer1[i] = -1
self.faceLayer2[i] = -1
self.faceLayer = self.faceLayer1
def __cinit__(self):
# Init tiny arrays for vertices and gradients at the vertices
self.vv = <double *>malloc(8 * sizeof(double))
self.vg = <double *>malloc(8*3 * sizeof(double))
# Init face layers
self.faceLayer1 = NULL
self.faceLayer2 = NULL
# Init vertices
self._vertexCount = 0
self._vertexMaxCount = 8
self._vertices = <float *>malloc(self._vertexMaxCount*3 * sizeof(float))
self._normals = <float *>malloc(self._vertexMaxCount*3 * sizeof(float))
self._values = <float *>malloc(self._vertexMaxCount * sizeof(float))
# Clear normals and values
cdef int i, j
if self._values is not NULL and self._normals is not NULL:
for i in range(self._vertexMaxCount):
self._values[i] = 0.0
for j in range(3):
self._normals[i*3+j] = 0.0
# Init faces
self._faceCount = 0
self._faceMaxCount = 8
self._faces = <int *>malloc(self._faceMaxCount * sizeof(int))
def __dealloc__(self):
free(self.vv)
free(self.vg)
free(self.faceLayer1)
free(self.faceLayer2)
free(self._vertices)
free(self._normals)
free(self._values)
free(self._faces)
cdef void _increase_size_vertices(self):
""" Increase the size of the vertices array by a factor two.
"""
# Allocate new array
cdef int newMaxCount = self._vertexMaxCount * 2
cdef float *newVertices = <float *>malloc(newMaxCount*3 * sizeof(float))
cdef float *newNormals = <float *>malloc(newMaxCount*3 * sizeof(float))
cdef float *newValues = <float *>malloc(newMaxCount * sizeof(float))
if newVertices is NULL or newNormals is NULL or newValues is NULL:
free(newVertices)
free(newNormals)
free(newValues)
raise MemoryError()
# Clear
cdef int i, j
for i in range(self._vertexCount, newMaxCount):
newValues[i] = 0.0
for j in range(3):
newNormals[i*3+j] = 0.0
# Copy
for i in range(self._vertexCount):
newValues[i] = self._values[i]
for j in range(3):
newVertices[i*3+j] = self._vertices[i*3+j]
newNormals[i*3+j] = self._normals[i*3+j]
# Apply
free(self._vertices); self._vertices = newVertices
free(self._normals); self._normals = newNormals
free(self._values); self._values = newValues
self._vertexMaxCount = newMaxCount
cdef void _increase_size_faces(self):
""" Increase the size of the faces array by a factor two.
"""
# Allocate new array
cdef int newMaxCount = self._faceMaxCount * 2
cdef int *newFaces = <int *>malloc(newMaxCount * sizeof(int))
if newFaces is NULL:
raise MemoryError()
# Copy
cdef int i
for i in range(self._faceCount):
newFaces[i] = self._faces[i]
# Apply
free(self._faces)
self._faces = newFaces
self._faceMaxCount = newMaxCount
## Adding results
cdef int add_vertex(self, float x, float y, float z):
""" Add a vertex to the result. Return index in vertex array.
"""
# Check if array is large enough
if self._vertexCount >= self._vertexMaxCount:
self._increase_size_vertices()
# Add vertex
self._vertices[self._vertexCount*3+0] = x
self._vertices[self._vertexCount*3+1] = y
self._vertices[self._vertexCount*3+2] = z
self._vertexCount += 1
return self._vertexCount -1
cdef void add_gradient(self, int vertexIndex, float gx, float gy, float gz):
""" Add a gradient value to the vertex corresponding to the given index.
"""
self._normals[vertexIndex*3+0] += gx
self._normals[vertexIndex*3+1] += gy
self._normals[vertexIndex*3+2] += gz
cdef void add_gradient_from_index(self, int vertexIndex, int i, float strength):
""" Add a gradient value to the vertex corresponding to the given index.
vertexIndex is the index in the large array of vertices that is returned.
i is the index of the array of vertices 0-7 for the current cell.
"""
self.add_gradient(vertexIndex, self.vg[i*3+0] * strength, self.vg[i*3+1] * strength, self.vg[i*3+2] * strength)
cdef add_face(self, int index):
""" Add a face to the result. Also updates the value.
"""
# Check if array is large enough
if self._faceCount >= self._faceMaxCount:
self._increase_size_faces()
# Add face
self._faces[self._faceCount] = index
self._faceCount += 1
# Also update value
if self.vmax > self._values[index]:
self._values[index] = self.vmax
## Getting results
def get_vertices(self):
""" Get the final vertex array.
"""
vertices = np.empty((self._vertexCount,3), np.float32)
cdef float [:, :] vertices_ = vertices
cdef int i, j
for i in range(self._vertexCount):
for j in range(3):
vertices_[i, j] = self._vertices[i*3+j]
return vertices
def get_normals(self):
""" Get the final normals array.
The normals are normalized to unit length.
"""
normals = np.empty((self._vertexCount,3), np.float32)
cdef float [:, :] normals_ = normals
cdef int i, j
cdef double length, dtmp
for i in range(self._vertexCount):
length = 0.0
for j in range(3):
dtmp = self._normals[i*3+j] # Make it double before taking **2!
length += dtmp*dtmp
if length > 0.0:
length = 1.0 / length**0.5
for j in range(3):
normals_[i,j] = self._normals[i*3+j] * length
return normals
def get_faces(self):
faces = np.empty((self._faceCount,), np.int32)
cdef int [:] faces_ = faces
cdef int i, j
for i in range(self._faceCount):
faces_[i] = self._faces[i]
return faces
def get_values(self):
values = np.empty((self._vertexCount,), np.float32)
cdef float [:] values_ = values
cdef int i, j
for i in range(self._vertexCount):
values_[i] = self._values[i]
return values
## Called from marching cube function
cdef void new_z_value(self):
""" This method should be called each time a new z layer is entered.
We will swap the layers with face information and empty the second.
"""
# Swap layers
self.faceLayer1, self.faceLayer2 = self.faceLayer2, self.faceLayer1
# Empty last
cdef int i
for i in range(self.nx*self.ny*4):
self.faceLayer2[i] = -1
cdef void set_cube(self, double isovalue, int x, int y, int z, int step,
double v0, double v1, double v2, double v3,
double v4, double v5, double v6, double v7):
""" Set the cube to the new location.
Set the values of the cube corners. The isovalue is subtracted
from them, such that in further calculations the isovalue can be
taken as zero.
This method also calculated the magic 256 word to identify the
cases (i.e. cell.index).
"""
# Set location and step
self.x = x
self.y = y
self.z = z
self.step = step
# Set values
self.v0 = v0 - isovalue
self.v1 = v1 - isovalue
self.v2 = v2 - isovalue
self.v3 = v3 - isovalue
self.v4 = v4 - isovalue
self.v5 = v5 - isovalue
self.v6 = v6 - isovalue
self.v7 = v7 - isovalue
# Calculate index
cdef int index = 0
if self.v0 > 0.0: index += 1
if self.v1 > 0.0: index += 2
if self.v2 > 0.0: index += 4
if self.v3 > 0.0: index += 8
if self.v4 > 0.0: index += 16
if self.v5 > 0.0: index += 32
if self.v6 > 0.0: index += 64
if self.v7 > 0.0: index += 128
self.index = index
# Reset c12
self.v12_calculated = 0
cdef void add_triangles(self, Lut lut, int lutIndex, int nt):
""" Add triangles.
The vertices for the triangles are specified in the given
Lut at the specified index. There are nt triangles.
The reason that nt should be given is because it is often known
beforehand.
"""
cdef int i, j
cdef int vi
self.prepare_for_adding_triangles()
for i in range(nt):
for j in range(3):
# Get two sides for each element in this vertex
vi = lut.get2(lutIndex, i*3+j)
self._add_face_from_edge_index(vi)
cdef void add_triangles2(self, Lut lut, int lutIndex, int lutIndex2, int nt):
""" Same as add_triangles, except that now the geometry is in a LUT
with 3 dimensions, and an extra index is provided.
"""
cdef int i, j
cdef int vi
self.prepare_for_adding_triangles()
for i in range(nt):
for j in range(3):
# Get two sides for each element in this vertex
vi = lut.get3(lutIndex, lutIndex2, i*3+j)
self._add_face_from_edge_index(vi)
## Used internally
cdef void _add_face_from_edge_index(self, int vi):
""" Add one face from an edge index. Only adds a face if the
vertex already exists. Otherwise also adds a vertex and applies
interpolation.
"""
# typedefs
cdef int indexInVertexArray, indexInFaceLayer
cdef int dx1, dy1, dz1
cdef int dx2, dy2, dz2
cdef int index1, index2
cdef double tmpf1, tmpf2
cdef double fx, fy, fz, ff
cdef double stp = <double>self.step
# Get index in the face layer and corresponding vertex number
indexInFaceLayer = self.get_index_in_facelayer(vi)
indexInVertexArray = self.faceLayer[indexInFaceLayer]
# If we have the center vertex, we have things pre-calculated,
# otherwise we need to interpolate.
# In both cases we distinguish between having this vertex already
# or not.
if vi == 12: # center vertex
if self.v12_calculated == 0:
self.calculate_center_vertex()
if indexInVertexArray >= 0:
# Vertex already calculated, only need to add face and gradient
self.add_face(indexInVertexArray)
self.add_gradient(indexInVertexArray, self.v12_xg, self.v12_yg, self.v12_zg)
else:
# Add precalculated center vertex position (is interpolated)
indexInVertexArray = self.add_vertex( self.v12_x, self.v12_y, self.v12_z)
# Update face layer
self.faceLayer[indexInFaceLayer] = indexInVertexArray
# Add face and gradient
self.add_face(indexInVertexArray)
self.add_gradient(indexInVertexArray, self.v12_xg, self.v12_yg, self.v12_zg)
else:
# Get relative edge indices for x, y and z
dx1, dx2 = self.luts.EDGESRELX.get2(vi,0), self.luts.EDGESRELX.get2(vi,1)
dy1, dy2 = self.luts.EDGESRELY.get2(vi,0), self.luts.EDGESRELY.get2(vi,1)
dz1, dz2 = self.luts.EDGESRELZ.get2(vi,0), self.luts.EDGESRELZ.get2(vi,1)
# Make two vertex indices
index1 = dz1*4 + dy1*2 + dx1
index2 = dz2*4 + dy2*2 + dx2
# Define strength of both corners
tmpf1 = 1.0 / (FLT_EPSILON + dabs(self.vv[index1]))
tmpf2 = 1.0 / (FLT_EPSILON + dabs(self.vv[index2]))
# print('indexInVertexArray', self.x, self.y, self.z, '-', vi, indexInVertexArray, indexInFaceLayer)
if indexInVertexArray >= 0:
# Vertex already calculated, only need to add face and gradient
self.add_face(indexInVertexArray)
self.add_gradient_from_index(indexInVertexArray, index1, tmpf1)
self.add_gradient_from_index(indexInVertexArray, index2, tmpf2)
else:
# Interpolate by applying a kind of center-of-mass method
fx, fy, fz, ff = 0.0, 0.0, 0.0, 0.0
fx += <double>dx1 * tmpf1; fy += <double>dy1 * tmpf1; fz += <double>dz1 * tmpf1; ff += tmpf1
fx += <double>dx2 * tmpf2; fy += <double>dy2 * tmpf2; fz += <double>dz2 * tmpf2; ff += tmpf2
# Add vertex
indexInVertexArray = self.add_vertex(
<double>self.x + stp*fx/ff,
<double>self.y + stp*fy/ff,
<double>self.z + stp*fz/ff )
# Update face layer
self.faceLayer[indexInFaceLayer] = indexInVertexArray
# Add face and gradient
self.add_face(indexInVertexArray)
self.add_gradient_from_index(indexInVertexArray, index1, tmpf1)
self.add_gradient_from_index(indexInVertexArray, index2, tmpf2)
# # Create vertex non-interpolated
# self.add_vertex( self.x + 0.5* dx1 + 0.5 * dx2,
# self.y + 0.5* dy1 + 0.5 * dy2,
# self.z + 0.5* dz1 + 0.5 * dz2 )
cdef int get_index_in_facelayer(self, int vi):
"""
Get the index of a vertex position, given the edge on which it lies.
We keep a list of faces so we can reuse vertices. This improves
speed because we need less interpolation, and the result is more
compact and can be visualized better because normals can be
interpolated.
For each cell, we store 4 vertex indices; all other edges can be
represented as the edge of another cell. The fourth is the center vertex.
This method returns -1 if no vertex has been defined yet.
vertices edges edge-indices per cell
* 7 ________ 6 _____6__ ________
* /| /| 7/| /| /| /|
* / | / | / | /5 | / | / |
* 4 /_______ / | /__4____ / 10 /_______ / |
* | | |5 | | 11 | | | | | |
* | 3|__|_____|2 | |__|__2__| | |__|_____|
* | / | / 8 3/ 9 / 2 / | /
* | / | / | / | /1 | 1 | /
* |/_______|/ |/___0___|/ |/___0___|/
* 0 1
*/
"""
# Init indices, both are corrected below
cdef int i = self.nx * self.y + self.x # Index of cube to get vertex at
cdef int j = 0 # Vertex number for that cell
cdef int vi_ = vi
cdef int *faceLayer
# Select either upper or lower half
if vi < 8:
# 8 horizontal edges
if vi < 4:
faceLayer = self.faceLayer1
else:
vi -= 4
faceLayer = self.faceLayer2
# Calculate actual index based on edge
#if vi == 0: pass # no step
if vi == 1: # step in x
i += self.step
j = 1
elif vi == 2: # step in y
i += self.nx * self.step
elif vi == 3: # no step
j = 1
elif vi < 12:
# 4 vertical edges
faceLayer = self.faceLayer1
j = 2
#if vi == 8: pass # no step
if vi == 9: # step in x
i += self.step
elif vi == 10: # step in x and y
i += self.nx * self.step + self.step
elif vi == 11: # step in y
i += self.nx * self.step
else:
# center vertex
faceLayer = self.faceLayer1
j = 3
# Store facelayer and return index
self.faceLayer = faceLayer # Dirty way of returning a value
return 4*i + j
cdef void prepare_for_adding_triangles(self):
""" Calculates some things to help adding the triangles:
array with corner values, max corner value, gradient at each corner.
"""
cdef int i
# Copy values in array so we can index them. Note the misalignment
# because the numbering does not correspond with bitwise OR of xyz.
self.vv[0] = self.v0
self.vv[1] = self.v1
self.vv[2] = self.v3#
self.vv[3] = self.v2#
self.vv[4] = self.v4
self.vv[5] = self.v5
self.vv[6] = self.v7#
self.vv[7] = self.v6#
# Calculate max
cdef double vmin, vmax
vmin, vmax = 0.0, 0.0
for i in range(8):
if self.vv[i] > vmax:
vmax = self.vv[i]
if self.vv[i] < vmin:
vmin = self.vv[i]
self.vmax = vmax-vmin
# Calculate gradients
# Derivatives, selected to always point in same direction.
# Note that many corners have the same components as other points,
# by interpolating and averaging the normals this is solved.
# todo: we can potentially reuse these similar to how we store vertex indices in face layers
self.vg[0*3+0], self.vg[0*3+1], self.vg[0*3+2] = self.v0-self.v1, self.v0-self.v3, self.v0-self.v4
self.vg[1*3+0], self.vg[1*3+1], self.vg[1*3+2] = self.v0-self.v1, self.v1-self.v2, self.v1-self.v5
self.vg[2*3+0], self.vg[2*3+1], self.vg[2*3+2] = self.v3-self.v2, self.v1-self.v2, self.v2-self.v6
self.vg[3*3+0], self.vg[3*3+1], self.vg[3*3+2] = self.v3-self.v2, self.v0-self.v3, self.v3-self.v7
self.vg[4*3+0], self.vg[4*3+1], self.vg[4*3+2] = self.v4-self.v5, self.v4-self.v7, self.v0-self.v4
self.vg[5*3+0], self.vg[5*3+1], self.vg[5*3+2] = self.v4-self.v5, self.v5-self.v6, self.v1-self.v5
self.vg[6*3+0], self.vg[6*3+1], self.vg[6*3+2] = self.v7-self.v6, self.v5-self.v6, self.v2-self.v6
self.vg[7*3+0], self.vg[7*3+1], self.vg[7*3+2] = self.v7-self.v6, self.v4-self.v7, self.v3-self.v7
cdef void calculate_center_vertex(self):
""" Calculate interpolated center vertex and its gradient.
"""
cdef double v0, v1, v2, v3, v4, v5, v6, v7
cdef double fx, fy, fz, ff
fx, fy, fz, ff = 0.0, 0.0, 0.0, 0.0
# Define "strength" of each corner of the cube that we need
v0 = 1.0 / (FLT_EPSILON + dabs(self.v0))
v1 = 1.0 / (FLT_EPSILON + dabs(self.v1))
v2 = 1.0 / (FLT_EPSILON + dabs(self.v2))
v3 = 1.0 / (FLT_EPSILON + dabs(self.v3))
v4 = 1.0 / (FLT_EPSILON + dabs(self.v4))
v5 = 1.0 / (FLT_EPSILON + dabs(self.v5))
v6 = 1.0 / (FLT_EPSILON + dabs(self.v6))
v7 = 1.0 / (FLT_EPSILON + dabs(self.v7))
# Apply a kind of center-of-mass method
fx += 0.0*v0; fy += 0.0*v0; fz += 0.0*v0; ff += v0
fx += 1.0*v1; fy += 0.0*v1; fz += 0.0*v1; ff += v1
fx += 1.0*v2; fy += 1.0*v2; fz += 0.0*v2; ff += v2
fx += 0.0*v3; fy += 1.0*v3; fz += 0.0*v3; ff += v3
fx += 0.0*v4; fy += 0.0*v4; fz += 1.0*v4; ff += v4
fx += 1.0*v5; fy += 0.0*v5; fz += 1.0*v5; ff += v5
fx += 1.0*v6; fy += 1.0*v6; fz += 1.0*v6; ff += v6
fx += 0.0*v7; fy += 1.0*v7; fz += 1.0*v7; ff += v7
# Store
cdef double stp = <double>self.step
self.v12_x = self.x + stp * fx / ff
self.v12_y = self.y + stp * fy / ff
self.v12_z = self.z + stp * fz / ff
# Also pre-calculate gradient of center
# note that prepare_for_adding_triangles() must have been called for
# the gradient data to exist.
self.v12_xg = ( v0*self.vg[0*3+0] + v1*self.vg[1*3+0] + v2*self.vg[2*3+0] + v3*self.vg[3*3+0] +
v4*self.vg[4*3+0] + v5*self.vg[5*3+0] + v6*self.vg[6*3+0] + v7*self.vg[7*3+0] )
self.v12_yg = ( v0*self.vg[0*3+1] + v1*self.vg[1*3+1] + v2*self.vg[2*3+1] + v3*self.vg[3*3+1] +
v4*self.vg[4*3+1] + v5*self.vg[5*3+1] + v6*self.vg[6*3+1] + v7*self.vg[7*3+1] )
self.v12_xg = ( v0*self.vg[0*3+2] + v1*self.vg[1*3+2] + v2*self.vg[2*3+2] + v3*self.vg[3*3+2] +
v4*self.vg[4*3+2] + v5*self.vg[5*3+2] + v6*self.vg[6*3+2] + v7*self.vg[7*3+2] )
# Set flag that this stuff is calculated
self.v12_calculated = 1
cdef class Lut:
""" Representation of a lookup table.
The tables are initially defined as numpy arrays. On initialization,
this class converts them to a C array for fast access.
This class defines functions to look up values using 1, 2 or 3 indices.
"""
cdef signed char* VALUES
cdef int L0 # Length
cdef int L1 # size of tuple
cdef int L2 # size of tuple in tuple (if any)
def __init__(self, array):
# Get the shape of the LUT
self.L1 = 1
self.L2 = 1
#
self.L0 = array.shape[0]
if array.ndim > 1:
self.L1 = array.shape[1]
if array.ndim > 2:
self.L2 = array.shape[2]
# Copy the contents
array = array.ravel()
cdef int n, N
N = self.L0 * self.L1 * self.L2
self.VALUES = <signed char *> malloc(N * sizeof(signed char))
if self.VALUES is NULL:
raise MemoryError()
for n in range(N):
self.VALUES[n] = array[n]
def __cinit__(self):
self.VALUES = NULL
def __dealloc__(self):
if self.VALUES is not NULL:
free(self.VALUES)
cdef int get1(self, int i0):
return self.VALUES[i0]
cdef int get2(self, int i0, int i1):
return self.VALUES[i0*self.L1 + i1]
cdef int get3(self, int i0, int i1, int i2):
return self.VALUES[i0*self.L1*self.L2 + i1*self.L2 + i2]
cdef class LutProvider:
""" Class that provides a common interface to the many lookup tables
used by the algorithm.
All the lists of lut names are autogenerated to prevent human error.
"""
cdef Lut EDGESRELX # Edges relative X
cdef Lut EDGESRELY
cdef Lut EDGESRELZ
cdef Lut CASESCLASSIC
cdef Lut CASES
cdef Lut TILING1
cdef Lut TILING2
cdef Lut TILING3_1
cdef Lut TILING3_2
cdef Lut TILING4_1
cdef Lut TILING4_2
cdef Lut TILING5
cdef Lut TILING6_1_1
cdef Lut TILING6_1_2
cdef Lut TILING6_2
cdef Lut TILING7_1
cdef Lut TILING7_2
cdef Lut TILING7_3
cdef Lut TILING7_4_1
cdef Lut TILING7_4_2
cdef Lut TILING8
cdef Lut TILING9
cdef Lut TILING10_1_1
cdef Lut TILING10_1_1_
cdef Lut TILING10_1_2
cdef Lut TILING10_2
cdef Lut TILING10_2_
cdef Lut TILING11
cdef Lut TILING12_1_1
cdef Lut TILING12_1_1_
cdef Lut TILING12_1_2
cdef Lut TILING12_2
cdef Lut TILING12_2_
cdef Lut TILING13_1
cdef Lut TILING13_1_
cdef Lut TILING13_2
cdef Lut TILING13_2_
cdef Lut TILING13_3
cdef Lut TILING13_3_
cdef Lut TILING13_4
cdef Lut TILING13_5_1
cdef Lut TILING13_5_2
cdef Lut TILING14
cdef Lut TEST3
cdef Lut TEST4
cdef Lut TEST6
cdef Lut TEST7
cdef Lut TEST10
cdef Lut TEST12
cdef Lut TEST13
cdef Lut SUBCONFIG13
def __init__(self, EDGESRELX, EDGESRELY, EDGESRELZ, CASESCLASSIC, CASES,
TILING1, TILING2, TILING3_1, TILING3_2, TILING4_1, TILING4_2,
TILING5, TILING6_1_1, TILING6_1_2, TILING6_2, TILING7_1, TILING7_2,
TILING7_3, TILING7_4_1, TILING7_4_2, TILING8, TILING9,
TILING10_1_1, TILING10_1_1_, TILING10_1_2, TILING10_2, TILING10_2_,
TILING11, TILING12_1_1, TILING12_1_1_, TILING12_1_2, TILING12_2,
TILING12_2_, TILING13_1, TILING13_1_, TILING13_2, TILING13_2_,
TILING13_3, TILING13_3_, TILING13_4, TILING13_5_1, TILING13_5_2,
TILING14,
TEST3, TEST4, TEST6, TEST7, TEST10, TEST12, TEST13,
SUBCONFIG13,
):
self.EDGESRELX = Lut(EDGESRELX)
self.EDGESRELY = Lut(EDGESRELY)
self.EDGESRELZ = Lut(EDGESRELZ)
self.CASESCLASSIC = Lut(CASESCLASSIC)
self.CASES = Lut(CASES)
self.TILING1 = Lut(TILING1)
self.TILING2 = Lut(TILING2)
self.TILING3_1 = Lut(TILING3_1)
self.TILING3_2 = Lut(TILING3_2)
self.TILING4_1 = Lut(TILING4_1)
self.TILING4_2 = Lut(TILING4_2)
self.TILING5 = Lut(TILING5)
self.TILING6_1_1 = Lut(TILING6_1_1)
self.TILING6_1_2 = Lut(TILING6_1_2)
self.TILING6_2 = Lut(TILING6_2)
self.TILING7_1 = Lut(TILING7_1)
self.TILING7_2 = Lut(TILING7_2)
self.TILING7_3 = Lut(TILING7_3)
self.TILING7_4_1 = Lut(TILING7_4_1)
self.TILING7_4_2 = Lut(TILING7_4_2)
self.TILING8 = Lut(TILING8)
self.TILING9 = Lut(TILING9)
self.TILING10_1_1 = Lut(TILING10_1_1)
self.TILING10_1_1_ = Lut(TILING10_1_1_)
self.TILING10_1_2 = Lut(TILING10_1_2)
self.TILING10_2 = Lut(TILING10_2)
self.TILING10_2_ = Lut(TILING10_2_)
self.TILING11 = Lut(TILING11)
self.TILING12_1_1 = Lut(TILING12_1_1)
self.TILING12_1_1_ = Lut(TILING12_1_1_)
self.TILING12_1_2 = Lut(TILING12_1_2)
self.TILING12_2 = Lut(TILING12_2)
self.TILING12_2_ = Lut(TILING12_2_)
self.TILING13_1 = Lut(TILING13_1)
self.TILING13_1_ = Lut(TILING13_1_)
self.TILING13_2 = Lut(TILING13_2)
self.TILING13_2_ = Lut(TILING13_2_)
self.TILING13_3 = Lut(TILING13_3)
self.TILING13_3_ = Lut(TILING13_3_)
self.TILING13_4 = Lut(TILING13_4)
self.TILING13_5_1 = Lut(TILING13_5_1)
self.TILING13_5_2 = Lut(TILING13_5_2)
self.TILING14 = Lut(TILING14)
self.TEST3 = Lut(TEST3)
self.TEST4 = Lut(TEST4)
self.TEST6 = Lut(TEST6)
self.TEST7 = Lut(TEST7)
self.TEST10 = Lut(TEST10)
self.TEST12 = Lut(TEST12)
self.TEST13 = Lut(TEST13)
self.SUBCONFIG13 = Lut(SUBCONFIG13)
def marching_cubes(float[:, :, :] im not None, double isovalue,
LutProvider luts, int st=1, int classic=0,
np.ndarray[np.npy_bool, ndim=3, cast=True] mask=None):
""" marching_cubes(im, double isovalue, LutProvider luts, int st=1, int classic=0)
Main entry to apply marching cubes.
Masked version of marching cubes. This function will check a
masking array (same size as im) to decide if the algorithm must be
computed for a given voxel. This adds a small overhead that
rapidly gets compensated by the fewer computed cubes
Returns (vertices, faces, normals, values)
"""
# Get dimemsnions
cdef int Nx, Ny, Nz
Nx, Ny, Nz = im.shape[2], im.shape[1], im.shape[0]
# Create cell to use throughout
cdef Cell cell = Cell(luts, Nx, Ny, Nz)
# Typedef variables
cdef int x, y, z, x_st, y_st, z_st
cdef int nt
cdef int case, config, subconfig
cdef bint no_mask = mask is None
# Unfortunately specifying a step in range() significantly degrades
# performance. Therefore we use a while loop.
# we have: max_x = Nx_bound + st + st - 1
# -> Nx_bound = max_allowable_x + 1 - 2 * st
# -> Nx_bound = Nx - 2 * st
assert st > 0
cdef int Nx_bound, Ny_bound, Nz_bound
Nx_bound, Ny_bound, Nz_bound = Nx - 2 * st, Ny - 2 * st, Nz - 2 * st # precalculated index range
z = -st
while z < Nz_bound:
z += st
z_st = z + st
cell.new_z_value() # Indicate that we enter a new layer
y = -st
while y < Ny_bound:
y += st
y_st = y + st
x = -st
while x < Nx_bound:
x += st
x_st = x + st
if no_mask or mask[z_st, y_st, x_st]:
# Initialize cell
cell.set_cube(isovalue, x, y, z, st,
im[z ,y, x], im[z ,y, x_st], im[z ,y_st, x_st], im[z ,y_st, x],
im[z_st,y, x], im[z_st,y, x_st], im[z_st,y_st, x_st], im[z_st,y_st, x] )
# Do classic!
if classic:
# Determine number of vertices
nt = 0
while luts.CASESCLASSIC.get2(cell.index, 3*nt) != -1:
nt += 1
# Add triangles
if nt > 0:
cell.add_triangles(luts.CASESCLASSIC, cell.index, nt)
else:
# Get case, if non-nul, enter the big switch
case = luts.CASES.get2(cell.index, 0)
if case > 0:
config = luts.CASES.get2(cell.index, 1)