def _plot_Sphere(self, os, **kwargs) -> Iterator[Artist]: from trimesh.creation import icosphere kwargs.setdefault('alpha', 0.5) ret = [] for o in os: tmesh = icosphere(radius=abs(o.radius)) loc = self._as_point_tuple_3d(o.location) t = Triangulation(loc[0] + tmesh.vertices[:, 0], loc[1] + tmesh.vertices[:, 1], triangles=tmesh.faces) yield self._ax.plot_trisurf(t, loc[2] + tmesh.vertices[:, 2], **kwargs)
def irregular( subdivisions=2, radius=0.0625, factor=0.1, seed=None, ): """Generates a mesh from an icosphere, by applying some randomly parametrized transformations. Args: subdivisions: subdivisions parameter of the icosphere radius: the mesh always fits within a sphere of this radius. factor: ratio of the maximum radius that corresponds to the mode distance from vertice to center. A smaller factor allows greater irregularity. seed: seed for the random number generator, or generator to be used. """ random = np.random.default_rng(seed) # Create icosphere mesh with max radius and given subdivisions mesh = creation.icosphere( subdivisions=subdivisions, radius=factor * radius, ) # Apply random vertex displacement in normal direction mesh.vertices += random.triangular( -factor * radius, 0, (1 - factor) * radius, (len(mesh.vertices), 1)) * mesh.vertex_normals # Get the convex hull mesh = mesh.convex_hull # pylint: disable=no-member # Align the minimum volume bounding box mesh.apply_obb() # Apply a random scaling in the direction of the smallest extent of # the bounding box. The scalling is only applied if the ratio between # smallest and largest extents is greater than factor. extents = mesh.bounding_box.extents direction = tuple(int(i == np.argmin(extents)) for i in range(3)) ratio = min(extents) / max(extents) if ratio > factor: mesh.apply_transform( transformations.scale_matrix( factor=random.triangular( factor / ratio, 1., # min(1., max(0.5, factor)/ratio), 1., ), # factor=random.uniform(factor/ratio, 1.), direction=direction, )) return mesh
def generate_sphere_icosahedron(subdivisions=3, radius=1.0): """ generate a sphere by subdividing an icosahedron simply call the trimesh function see trimesh.creation.icosphere for more details :param subdivisions: int How many times to subdivide the mesh. Note that the number of faces will grow as function of 4 ** subdivisions, so you probably want to keep this under ~5 :param radius: float Desired radius of sphere :return: """ return tcr.icosphere(subdivisions=subdivisions, radius=radius)
def reconstructed_surface_icosphere(coefficients, real=True, subdivisions=3): if real: n = len(coefficients) l_max = int((-3 + np.sqrt(8 * n + 1)) // 2) else: raise NotImplementedError( "Complex reconstructed surface case not yet implemented") LOG.debug("Reconstructing deduced l_max = %d", l_max) sht = SHT(l_max) from trimesh.creation import icosphere sphere = icosphere(subdivisions=subdivisions) theta = np.arccos(sphere.vertices[:, 2]) phi = np.arctan2(sphere.vertices[:, 1], sphere.vertices[:, 0]) r = np.empty_like(phi) for i in range(phi.shape[0]): r[i] = sht.evaluate_at_points(coefficients, theta[i], phi[i]) sphere.vertices *= r[:, np.newaxis] return sphere
def getLBP3DImage(inputImage, inputMask, **kwargs): """ Compute and return the Local Binary Pattern (LBP) in 3D using spherical harmonics. If ``force2D`` is set to true (= feature extraction in 2D) a warning is logged. LBP is only calculated for voxels segmented in the mask Following settings are possible: - ``lbp3DLevels`` [2]: integer, specifies the the number of levels in spherical harmonics to use. - ``lbp3DIcosphereRadius`` [1]: Float, specifies the radius in which the neighbours should be sampled - ``lbp3DIcosphereSubdivision`` [1]: Integer, specifies the number of subdivisions to apply in the icosphere :return: Yields LBP filtered image for each level, 'lbp-3D-m<level>' and ``kwargs`` (customized settings). Additionally yields the kurtosis image, 'lbp-3D-k' and ``kwargs``. .. note:: LBP can often return only a very small number of different gray levels. A customized bin width is often needed. .. warning:: Requires package ``scipy`` and ``trimesh`` to function. If not available, this filter logs a warning and does not yield an image. References: - Banerjee, J, Moelker, A, Niessen, W.J, & van Walsum, T.W. (2013), "3D LBP-based rotationally invariant region description." In: Park JI., Kim J. (eds) Computer Vision - ACCV 2012 Workshops. ACCV 2012. Lecture Notes in Computer Science, vol 7728. Springer, Berlin, Heidelberg. doi:10.1007/978-3-642-37410-4_3 """ global logger try: from scipy.stats import kurtosis from scipy.ndimage.interpolation import map_coordinates from scipy.special import sph_harm from trimesh.creation import icosphere except ImportError: logger.warning('Could not load required package "scipy" or "trimesh", cannot implement filter LBP 3D') return # Warn the user if features are extracted in 2D, as this function calculates LBP in 3D if kwargs.get('force2D', False): logger.warning('Calculating Local Binary Pattern in 3D, but extracting features in 2D. Use with caution!') label = kwargs.get('label', 1) lbp_levels = kwargs.get('lbp3DLevels', 2) lbp_icosphereRadius = kwargs.get('lbp3DIcosphereRadius', 1) lbp_icosphereSubdivision = kwargs.get('lbp3DIcosphereSubdivision', 1) im_arr = sitk.GetArrayFromImage(inputImage) ma_arr = sitk.GetArrayFromImage(inputMask) # Variables used in the shape comments: # Np Number of voxels # Nv Number of vertices # Vertices icosahedron for spherical sampling coords_icosahedron = numpy.array(icosphere(lbp_icosphereSubdivision, lbp_icosphereRadius).vertices) # shape(Nv, 3) # Corresponding polar coordinates theta = numpy.arccos(numpy.true_divide(coords_icosahedron[:, 2], lbp_icosphereRadius)) phi = numpy.arctan2(coords_icosahedron[:, 1], coords_icosahedron[:, 0]) # Corresponding spherical harmonics coefficients Y_{m, n, theta, phi} Y = sph_harm(0, 0, theta, phi) # shape(Nv,) n_ix = numpy.array(0) for n in range(1, lbp_levels): for m in range(-n, n + 1): n_ix = numpy.append(n_ix, n) Y = numpy.column_stack((Y, sph_harm(m, n, theta, phi))) # shape (Nv, x) where x is the number of iterations in the above loops + 1 # Get labelled coordinates ROI_coords = numpy.where(ma_arr == label) # shape(3, Np) # Interpolate f (samples on the spheres across the entire volume) coords = numpy.array(ROI_coords).T[None, :, :] + coords_icosahedron[:, None, :] # shape(Nv, Np, 3) f = map_coordinates(im_arr, coords.T, order=3) # Shape(Np, Nv) Note that 'Np' and 'Nv' are swapped due to .T # Compute spherical Kurtosis k = kurtosis(f, axis=1) # shape(Np,) # Apply sign function f_centroids = im_arr[ROI_coords] # Shape(Np,) f = numpy.greater_equal(f, f_centroids[:, None]).astype(int) # Shape(Np, Nv) # Compute c_{m,n} coefficients c = numpy.multiply(f[:, :, None], Y[None, :, :]) # Shape(Np, Nv, x) c = c.sum(axis=1) # Shape(Np, x) # Integrate over m f = numpy.multiply(c[:, None, n_ix == 0], Y[None, :, n_ix == 0]) # Shape (Np, Nv, 1) for n in range(1, lbp_levels): f = numpy.concatenate((f, numpy.sum(numpy.multiply(c[:, None, n_ix == n], Y[None, :, n_ix == n]), axis=2, keepdims=True) ), axis=2) # Shape f (Np, Nv, levels) # Compute L2-Norm f = numpy.sqrt(numpy.sum(f ** 2, axis=1)) # shape(Np, levels) # Keep only Real Part f = numpy.real(f) # shape(Np, levels) k = numpy.real(k) # shape(Np,) # Yield the derived images for each level result = numpy.ndarray(im_arr.shape) for l_idx in range(lbp_levels): result[ROI_coords] = f[:, l_idx] # Create a SimpleITK image im = sitk.GetImageFromArray(result) im.CopyInformation(inputImage) yield im, 'lbp-3D-m%d' % (l_idx + 1), kwargs # Yield Kurtosis result[ROI_coords] = k # Create a SimpleITK image im = sitk.GetImageFromArray(result) im.CopyInformation(inputImage) yield im, 'lbp-3D-k', kwargs
import numpy as np import matplotlib.pyplot as plt from mayavi import mlab import trimesh from bfieldtools.suhtools import SuhBasis from bfieldtools.utils import find_mesh_boundaries from trimesh.creation import icosphere from bfieldtools.utils import load_example_mesh # Import meshes # Sphere sphere = icosphere(4, 0.1) # Plane mesh centered on the origin plane = load_example_mesh("10x10_plane_hires") scaling_factor = 0.02 plane.apply_scale(scaling_factor) # Rotate to x-plane t = np.eye(4) t[1:3, 1:3] = np.array([[0, 1], [-1, 0]]) plane.apply_transform(t) # Bunny bunny = load_example_mesh("bunny_repaired") bunny.vertices -= bunny.vertices.mean(axis=0) mlab.figure(bgcolor=(1, 1, 1))
def _get_node_sphere(node, radius=0.5): sp = icosphere(subdivisions=2) sp.apply_scale(radius) sp.apply_translation(node) return sp
) from bfieldtools.mesh_magnetics import scalar_potential_coupling as compute_U from bfieldtools.mesh_impedance import mutual_inductance_matrix from bfieldtools.contour import scalar_contour from bfieldtools import sphtools from bfieldtools.utils import load_example_mesh from bfieldtools.mesh_calculus import mass_matrix # domain = 'sphere' # domain = 'cube' domain = "combined" if domain == "sphere": from trimesh.creation import icosphere mesh1 = icosphere(3, 0.65) mesh2 = icosphere(3, 0.8) elif domain == "cube": from trimesh.creation import box from trimesh.smoothing import filter_laplacian mesh1 = box((1.0, 1.0, 1.0)) mesh2 = box((1.5, 1.5, 1.5)) for i in range(4): mesh1 = mesh1.subdivide() mesh2 = mesh2.subdivide() mesh1 = filter_laplacian(mesh1) mesh2 = filter_laplacian(mesh2, 0.9) elif domain == "combined":