def mesh_edges(surf: Union[dict, BSPolyData, "SLM", Nifti1Image], mask: Optional[ArrayLike] = None) -> np.ndarray: """Converts the triangles or lattices of a mesh to edges. Parameters ---------- surf : dict, BSPolyData, SLM, Nifti1Image One of the following: - A dictionary with key 'tri' where tri is numpy array of triangle indices, t:#triangles. Note that, for compatibility with SurfStat, these triangles are 1-indexed, not 0-indexed. - A dictionary with key 'lat' where lat is a 3D numpy array of 1's and 0's (1:in, 0:out). - A BrainSpace surface object - An SLM object with an associated surface. Returns ------- np.ndarray A e-by-2 numpy array containing the indices of the edges, where e is the number of edges. Note that these are 0-indexed. """ # Convert all inputs to a compatible dictionary, retain BSPolyData. if type(surf).__name__ == "SLM": if surf.tri is not None: # type: ignore edg = triangles_to_edges(surf.tri) # type: ignore elif surf.lat is not None: # type: ignore edg = lattice_to_edges(surf.lat) # type: ignore else: ValueError("SLM object does not have triangle/lattice data.") elif isinstance(surf, dict): if "tri" in surf: edg = triangles_to_edges(surf["tri"]) if "lat" in surf: edg = lattice_to_edges(surf["lat"]) elif isinstance(surf, Nifti1Image): if mask is not None: raise ValueError( "Masks are currently not compatible with a NIFTI image lattice input." ) edg = lattice_to_edges(surf.get_fdata() != 0) elif isinstance(surf, BSPolyData): edg = get_edges(surf) else: raise ValueError("Unknown surface format.") if mask is not None: edg, _ = _mask_edges(edg, mask) return edg
def generate_random_slm(surf, n_var=1, dfs=None, mask=None, cluster_threshold=0.001): """Generates a valid SLM for a surface. Parameters ---------- surf : BSPolyData Brain surface. n_var : int, optional slm.k, by default 1. dfs : np.array, None, optional Effective degrees of freedom, by default None. mask : np.array, optional Boolean mask, by default None. cluster_threshold : float, optional Cluster threshold, by default 0.001. Returns ------- brainstat.stats.SLM SLM object. """ edges = get_edges(surf) vertices = get_points(surf) n_vertices = vertices.shape[0] n_edges = edges.shape[0] slm = generate_slm( t=np.random.random_sample((1, n_vertices)), df=np.random.randint(2, 100), k=n_var, resl=np.random.random_sample((n_edges, 1)), surf=surf, dfs=dfs, mask=mask, cluster_threshold=cluster_threshold, ) return slm
def test_mesh_elements(): s = _generate_sphere() ee = vtk.vtkExtractEdges() ee.SetInputData(s.VTKObject) ee.Update() ee = wrap_vtk(ee.GetOutput()) n_edges = ee.n_cells assert np.all(me.get_points(s) == s.Points) assert np.all(me.get_cells(s) == s.GetCells2D()) assert me.get_extent(s).shape == (3, ) pc = me.get_point2cell_connectivity(s) assert pc.shape == (s.n_points, s.n_cells) assert pc.dtype == np.uint8 assert np.all(pc.sum(axis=0) == 3) cp = me.get_cell2point_connectivity(s) assert pc.dtype == np.uint8 assert (pc - cp.T).nnz == 0 adj = me.get_immediate_adjacency(s) assert adj.shape == (s.n_points, s.n_points) assert adj.dtype == np.uint8 assert adj.nnz == (2 * n_edges + s.n_points) adj2 = me.get_immediate_adjacency(s, include_self=False) assert adj2.shape == (s.n_points, s.n_points) assert adj2.dtype == np.uint8 assert adj2.nnz == (2 * n_edges) radj = me.get_ring_adjacency(s) assert radj.dtype == np.uint8 assert (adj - radj).nnz == 0 radj2 = me.get_ring_adjacency(s, include_self=False) assert radj2.dtype == np.uint8 assert (adj2 - radj2).nnz == 0 radj3 = me.get_ring_adjacency(s, n_ring=2, include_self=False) assert radj3.dtype == np.uint8 assert (radj3 - adj2).nnz > 0 d = me.get_immediate_distance(s) assert d.shape == (s.n_points, s.n_points) assert d.dtype == np.float assert d.nnz == adj2.nnz d2 = me.get_immediate_distance(s, metric='sqeuclidean') d_sq = d.copy() d_sq.data **= 2 assert np.allclose(d_sq.A, d2.A) rd = me.get_ring_distance(s) assert rd.dtype == np.float assert np.allclose(d.A, rd.A) rd2 = me.get_ring_distance(s, n_ring=2) assert (rd2 - d).nnz > 0 assert me.get_cell_neighbors(s).shape == (s.n_cells, s.n_cells) assert me.get_edges(s).shape == (n_edges, 2) assert me.get_edge_length(s).shape == (n_edges, ) assert me.get_boundary_points(s).size == 0 assert me.get_boundary_edges(s).size == 0 assert me.get_boundary_cells(s).size == 0
def py_SurfStatEdg(surf): """Converts the triangles or lattices of a mesh to edges. Args: surf (dict): = a dictionary with key 'tri' or 'lat' surf['tri'] = (t x 3) numpy array of triangle indices, t:#triangles, or, surf['lat'] = 3D numpy array of 1's and 0's (1:in, 0:out). or surf (BSPolyData) = a BrainSpace surface object. Returns: edg (np.array): A e-by-2 numpy array containing the indices of the edges, where e is the number of edges. """ # For BSPolyData, simply use BrainSpace's functionality to grab edges. if isinstance(surf, BSPolyData): edg = get_edges(surf) # Convert triangles to edges by grabbing all unique edges within triangles. elif 'tri' in surf: tri = np.sort(surf['tri'], axis=1) edg = np.unique(np.concatenate((np.concatenate((tri[:,[0, 1]], \ tri[:,[0, 2]])), tri[:,[1, 2]] )) , axis=0) edg = edg - 1 elif 'lat' in surf: # See the comments of SurfStatResels for a full explanation. if surf['lat'].ndim == 2: surf['lat'] = np.expand_dims(surf['lat'], axis=2) I, J, K = np.shape(surf['lat']) IJ = I * J a = np.arange(1, int(I) + 1, dtype='int') b = np.arange(1, int(J) + 1, dtype='int') i, j = np.meshgrid(a, b) i = i.T.flatten('F') j = j.T.flatten('F') n1 = (I - 1) * (J - 1) * 6 + (I - 1) * 3 + (J - 1) * 3 + 1 n2 = (I - 1) * (J - 1) * 3 + (I - 1) + (J - 1) edg = np.zeros(((K - 1) * n1 + n2, int(2)), dtype='int') for f in range(0, 2): c1 = np.where((np.remainder((i + j), 2) == f) & (i < I) & (j < J))[0] c2 = np.where((np.remainder((i + j), 2) == f) & (i > 1) & (j < J))[0] c11 = np.where((np.remainder((i + j), 2) == f) & (i == I) & (j < J))[0] c21 = np.where((np.remainder((i + j), 2) == f) & (i == I) & (j > 1))[0] c12 = np.where((np.remainder((i + j), 2) == f) & (i < I) & (j == J))[0] c22 = np.where((np.remainder((i + j), 2) == f) & (i > 1) & (j == J))[0] # bottom slice edg0 = np.block([[ c1, c1, c1, c2-1, c2-1, c2, c11, c21-I, c12, \ c22-1 ], [ c1+1, c1+I, c1+1+I, c2, c2-1+I, c2-1+I, \ c11+I, c21, c12+1, c22 ]]).T +1 # between slices edg1 = np.block([[ c1, c1, c1, c11, c11, c12, c12], [c1+IJ, c1+1+IJ, \ c1+I+IJ, c11+IJ, c11+I+IJ, c12+IJ, c12+1+IJ ]]).T +1 edg2 = np.block([[c2-1, c2, c2-1+I, c21-I, c21, c22-1, c22], \ [c2-1+IJ, c2-1+IJ, c2-1+IJ, c21-I+IJ, c21-I+IJ, \ c22-1+IJ, c22-1+IJ]]).T +1 if f: for k in colon(2, K - 1, 2): edg[(k-1)*n1 + np.arange(0,n1), :] = (np.block([[edg0], \ [edg2], [edg1], [IJ, 2*IJ]]) + (k-1) *IJ) else: for k in colon(1, K - 1, 2): edg[(k-1)*n1 + np.arange(0,n1), :] = (np.block([[edg0], \ [edg1], [edg2], [IJ, 2*IJ]]) + (k-1) *IJ) if np.remainder((K + 1), 2) == f: # top slice edg[ (K-1)*n1 + np.arange(0,n2), :] = \ edg0[np.arange(0,n2),:] + (K-1) * IJ # index by voxels in the "lat" vid = np.array(np.multiply(np.cumsum(surf['lat'][:].T.flatten()), \ surf['lat'][:].T.flatten()) , dtype='int') vid = vid.reshape(len(vid), 1) # only inside the lat all_idx = np.all(np.block([[surf['lat'].T.flatten()[edg[:,0] -1]], \ [surf['lat'].T.flatten()[edg[:,1] -1]]]).T, axis=1) edg = vid[edg[all_idx, :] - 1].reshape(np.shape(edg[all_idx, :] - 1)) edg = edg - 1 else: sys.exit( 'Input "surf" must have "lat" or "tri" key, or be a mesh object.') return edg
def mesh_edges(surf, mask=None): """Converts the triangles or lattices of a mesh to edges. Args: surf (dict): = a dictionary with key 'tri' or 'lat' surf['tri'] = (t x 3) numpy array of triangle indices, t:#triangles, or, surf['lat'] = 3D numpy array of 1's and 0's (1:in, 0:out). or surf (BSPolyData) = a BrainSpace surface object or surf (SLM) = a SLM object with an associated surface. Returns: edg (np.array): A e-by-2 numpy array containing the indices of the edges, where e is the number of edges. """ # This doesn't strictly test that its BrainStat SLM, but we can't import # directly without causing a circular import. class_name = surf.__class__.__name__ if class_name is "SLM": if surf.tri is not None: surf = {"tri": surf.tri} elif surf.lat is not None: surf = {"lat": surf.lat} elif surf.surf is not None: return mesh_edges(surf.surf) else: ValueError("SLM object does not have triangle/lattice data.") # For BSPolyData, simply use BrainSpace's functionality to grab edges. if isinstance(surf, BSPolyData): edg = get_edges(surf) # Convert triangles to edges by grabbing all unique edges within triangles. elif "tri" in surf: tri = np.sort(surf["tri"], axis=1) edg = np.unique( np.concatenate( (np.concatenate((tri[:, [0, 1]], tri[:, [0, 2]])), tri[:, [1, 2]]) ), axis=0, ) edg = edg - 1 elif "lat" in surf: # See the comments of SurfStatResels for a full explanation. if surf["lat"].ndim == 2: surf["lat"] = np.expand_dims(surf["lat"], axis=2) I, J, K = np.shape(surf["lat"]) IJ = I * J a = np.arange(1, int(I) + 1, dtype="int") b = np.arange(1, int(J) + 1, dtype="int") i, j = np.meshgrid(a, b) i = i.T.flatten("F") j = j.T.flatten("F") n1 = (I - 1) * (J - 1) * 6 + (I - 1) * 3 + (J - 1) * 3 + 1 n2 = (I - 1) * (J - 1) * 3 + (I - 1) + (J - 1) edg = np.zeros(((K - 1) * n1 + n2, int(2)), dtype="int") for f in range(0, 2): c1 = np.where((np.remainder((i + j), 2) == f) & (i < I) & (j < J))[0] c2 = np.where((np.remainder((i + j), 2) == f) & (i > 1) & (j < J))[0] c11 = np.where((np.remainder((i + j), 2) == f) & (i == I) & (j < J))[0] c21 = np.where((np.remainder((i + j), 2) == f) & (i == I) & (j > 1))[0] c12 = np.where((np.remainder((i + j), 2) == f) & (i < I) & (j == J))[0] c22 = np.where((np.remainder((i + j), 2) == f) & (i > 1) & (j == J))[0] # bottom slice edg0 = ( np.block( [ [c1, c1, c1, c2 - 1, c2 - 1, c2, c11, c21 - I, c12, c22 - 1], [ c1 + 1, c1 + I, c1 + 1 + I, c2, c2 - 1 + I, c2 - 1 + I, c11 + I, c21, c12 + 1, c22, ], ] ).T + 1 ) # between slices edg1 = ( np.block( [ [c1, c1, c1, c11, c11, c12, c12], [ c1 + IJ, c1 + 1 + IJ, c1 + I + IJ, c11 + IJ, c11 + I + IJ, c12 + IJ, c12 + 1 + IJ, ], ] ).T + 1 ) edg2 = ( np.block( [ [c2 - 1, c2, c2 - 1 + I, c21 - I, c21, c22 - 1, c22], [ c2 - 1 + IJ, c2 - 1 + IJ, c2 - 1 + IJ, c21 - I + IJ, c21 - I + IJ, c22 - 1 + IJ, c22 - 1 + IJ, ], ] ).T + 1 ) if f: for k in colon(2, K - 1, 2): edg[(k - 1) * n1 + np.arange(0, n1), :] = ( np.block([[edg0], [edg2], [edg1], [IJ, 2 * IJ]]) + (k - 1) * IJ ) else: for k in colon(1, K - 1, 2): edg[(k - 1) * n1 + np.arange(0, n1), :] = ( np.block([[edg0], [edg1], [edg2], [IJ, 2 * IJ]]) + (k - 1) * IJ ) if np.remainder((K + 1), 2) == f: # top slice edg[(K - 1) * n1 + np.arange(0, n2), :] = ( edg0[np.arange(0, n2), :] + (K - 1) * IJ ) # index by voxels in the "lat" vid = np.array( np.multiply( np.cumsum(surf["lat"][:].T.flatten()), surf["lat"][:].T.flatten() ), dtype="int", ) vid = vid.reshape(len(vid), 1) # only inside the lat all_idx = np.all( np.block( [ [surf["lat"].T.flatten()[edg[:, 0] - 1]], [surf["lat"].T.flatten()[edg[:, 1] - 1]], ] ).T, axis=1, ) edg = vid[edg[all_idx, :] - 1].reshape(np.shape(edg[all_idx, :] - 1)) edg = edg - 1 else: sys.exit('Input "surf" must have "lat" or "tri" key, or be a mesh object.') if mask is not None: edg, _ = _mask_edges(edg, mask) return edg