@@ -39,6 +39,8 @@ class Surface(object):
39
39
The vertices coordinates
40
40
faces : 2d array
41
41
The faces ie. the triangles
42
+ nn : 2d array
43
+ Normalized surface normals for vertices.
42
44
subjects_dir : str | None
43
45
If not None, this directory will be used as the subjects directory
44
46
instead of the value set using the SUBJECTS_DIR environment variable.
@@ -80,6 +82,7 @@ def load_geometry(self):
80
82
self .coords [:, 0 ] -= (np .max (self .coords [:, 0 ]) + self .offset )
81
83
else :
82
84
self .coords [:, 0 ] -= (np .min (self .coords [:, 0 ]) + self .offset )
85
+ self .nn = _compute_normals (self .coords , self .faces )
83
86
84
87
def save_geometry (self ):
85
88
surf_path = op .join (self .data_path , "surf" ,
@@ -128,6 +131,77 @@ def apply_xfm(self, mtx):
128
131
mtx .T )[:, :3 ]
129
132
130
133
134
+ def _fast_cross_3d (x , y ):
135
+ """Compute cross product between list of 3D vectors
136
+
137
+ Much faster than np.cross() when the number of cross products
138
+ becomes large (>500). This is because np.cross() methods become
139
+ less memory efficient at this stage.
140
+
141
+ Parameters
142
+ ----------
143
+ x : array
144
+ Input array 1.
145
+ y : array
146
+ Input array 2.
147
+
148
+ Returns
149
+ -------
150
+ z : array
151
+ Cross product of x and y.
152
+
153
+ Notes
154
+ -----
155
+ x and y must both be 2D row vectors. One must have length 1, or both
156
+ lengths must match.
157
+ """
158
+ assert x .ndim == 2
159
+ assert y .ndim == 2
160
+ assert x .shape [1 ] == 3
161
+ assert y .shape [1 ] == 3
162
+ assert (x .shape [0 ] == 1 or y .shape [0 ] == 1 ) or x .shape [0 ] == y .shape [0 ]
163
+ if max ([x .shape [0 ], y .shape [0 ]]) >= 500 :
164
+ return np .c_ [x [:, 1 ] * y [:, 2 ] - x [:, 2 ] * y [:, 1 ],
165
+ x [:, 2 ] * y [:, 0 ] - x [:, 0 ] * y [:, 2 ],
166
+ x [:, 0 ] * y [:, 1 ] - x [:, 1 ] * y [:, 0 ]]
167
+ else :
168
+ return np .cross (x , y )
169
+
170
+
171
+ def _compute_normals (rr , tris ):
172
+ """Efficiently compute vertex normals for triangulated surface"""
173
+ # first, compute triangle normals
174
+ r1 = rr [tris [:, 0 ], :]
175
+ r2 = rr [tris [:, 1 ], :]
176
+ r3 = rr [tris [:, 2 ], :]
177
+ tri_nn = _fast_cross_3d ((r2 - r1 ), (r3 - r1 ))
178
+
179
+ # Triangle normals and areas
180
+ size = np .sqrt (np .sum (tri_nn * tri_nn , axis = 1 ))
181
+ zidx = np .where (size == 0 )[0 ]
182
+ size [zidx ] = 1.0 # prevent ugly divide-by-zero
183
+ tri_nn /= size [:, np .newaxis ]
184
+
185
+ npts = len (rr )
186
+
187
+ # the following code replaces this, but is faster (vectorized):
188
+ #
189
+ # for p, verts in enumerate(tris):
190
+ # nn[verts, :] += tri_nn[p, :]
191
+ #
192
+ nn = np .zeros ((npts , 3 ))
193
+ for verts in tris .T : # note this only loops 3x (number of verts per tri)
194
+ counts = np .bincount (verts , minlength = npts )
195
+ reord = np .argsort (verts )
196
+ vals = np .r_ [np .zeros ((1 , 3 )), np .cumsum (tri_nn [reord , :], 0 )]
197
+ idx = np .cumsum (np .r_ [0 , counts ])
198
+ nn += vals [idx [1 :], :] - vals [idx [:- 1 ], :]
199
+ size = np .sqrt (np .sum (nn * nn , axis = 1 ))
200
+ size [size == 0 ] = 1.0 # prevent ugly divide-by-zero
201
+ nn /= size [:, np .newaxis ]
202
+ return nn
203
+
204
+
131
205
###############################################################################
132
206
# LOGGING (courtesy of mne-python)
133
207
0 commit comments