Example #1
0
def test_wrong_method():

    with pytest.raises(ValueError):
        mcubes.smooth(
            np.zeros((10, 10), dtype=np.bool_),
            method='wrong',
            max_iters=500,
            rel_tol=1e-4
        )
Example #2
0
def test_sphere():

    # Create sphere with radius 25 centered at (50, 50, 50)
    x, y, z = np.mgrid[:100, :100, :100]
    levelset = np.sqrt((x - 50)**2 + (y - 50)**2 + (z - 50)**2) - 25

    # vertices, triangles = mcubes.marching_cubes(levelset, 0)
    # mcubes.export_obj(vertices, triangles, 'sphere1.obj')

    binary_levelset = levelset > 0
    smoothed_levelset = mcubes.smooth(
        binary_levelset,
        method='constrained',
        max_iters=500,
        rel_tol=1e-4
    )

    vertices, _ = mcubes.marching_cubes(smoothed_levelset, 0.0)

    # Check all vertices have same distance to (50, 50, 50)
    dist = np.sqrt(np.sum((vertices - [50, 50, 50])**2, axis=1))

    assert dist.min() > 24.5 and dist.max() < 25.5
    assert np.all(np.abs(smoothed_levelset - levelset) < 1)
    assert np.all((smoothed_levelset > 0) == binary_levelset)
Example #3
0
def test_wrong_ndim():

    binary_levelset = np.random.uniform(size=(10)) < 0.5

    with pytest.raises(ValueError):
        mcubes.smooth(
            binary_levelset,
            method='constrained',
            max_iters=500,
            rel_tol=1e-4
        )

    binary_levelset = np.random.uniform(size=(10, 10, 10, 10)) < 0.5

    with pytest.raises(ValueError):
        mcubes.smooth(
            binary_levelset,
            method='constrained',
            max_iters=500,
            rel_tol=1e-4
        )
Example #4
0
def test_circle():

    x, y = np.mgrid[:100, :100]
    levelset = np.sqrt((x - 50)**2 + (y - 50)**2) - 25
    binary_levelset = levelset > 0

    smoothed_levelset = mcubes.smooth(
        binary_levelset,
        max_iters=500,
        rel_tol=1e-4
    )

    assert np.all(np.abs(smoothed_levelset - levelset) < 1)
    assert np.all((smoothed_levelset > 0) == binary_levelset)
Example #5
0
def smooth(volume: np.ndarray, mode: str) -> Tuple[np.ndarray, float]:

    isovalue = 0.0
    if mode == 'auto':
        volume = mcubes.smooth(volume)
    elif mode == 'constrained':
        volume = mcubes.smooth_constrained(volume, max_iters=500)
    elif mode == 'gaussian':
        volume = mcubes.smooth_gaussian(volume)
    elif mode == 'no':
        isovalue = 0.5
    else:
        raise ValueError("unknown mode '{}'".format(mode))

    return volume, isovalue
Example #6
0
def test_gaussian_smoothing():

    # Create sphere with radius 25 centered at (50, 50, 50)
    x, y, z = np.mgrid[:100, :100, :100]
    levelset = np.sqrt((x - 50)**2 + (y - 50)**2 + (z - 50)**2) - 25

    binary_levelset = levelset > 0
    smoothed_levelset = mcubes.smooth(
        binary_levelset,
        method='gaussian',
        sigma=3
    )

    vertices, _ = mcubes.marching_cubes(smoothed_levelset, 0.0)

    # Check all vertices have same distance to (50, 50, 50)
    dist = np.sqrt(np.sum((vertices - [50, 50, 50])**2, axis=1))
    assert dist.min() > 24 and dist.max() < 25
Example #7
0
    def marchingCubes(cls,
                      voxel_model: VoxelModel,
                      smooth: bool = False,
                      color: Tuple[float, float, float,
                                   float] = (0.8, 0.8, 0.8, 1)):
        """
        Generate a mesh object from a VoxelModel object using a marching cubes algorithm.

        This meshing approach is best suited to high resolution models where some smoothing is acceptable.

        Args:
            voxel_model: VoxelModel object to be converted to a mesh
            smooth: Enable smoothing
            color: Mesh color in the format (r, g, b, a)
        
        Returns:
            None
        """
        voxel_model_fit = voxel_model.fitWorkspace().getOccupied()
        voxels = voxel_model_fit.voxels.astype(np.uint16)
        x, y, z = voxels.shape
        model_offsets = voxel_model_fit.coords

        voxels_padded = np.zeros((x + 2, y + 2, z + 2))
        voxels_padded[1:-1, 1:-1, 1:-1] = voxels

        if smooth:
            voxels_padded = mcubes.smooth(voxels_padded)
            levelset = 0
        else:
            levelset = 0.5

        verts, tris = mcubes.marching_cubes(voxels_padded, levelset)

        # Shift model to align with origin
        verts = np.subtract(verts, 0.5)
        verts[:, 0] = np.add(verts[:, 0], model_offsets[0])
        verts[:, 1] = np.add(verts[:, 1], model_offsets[1])
        verts[:, 2] = np.add(verts[:, 2], model_offsets[2])

        verts_colors = generateColors(len(verts), color)

        return cls(voxels_padded, verts, verts_colors, tris,
                   voxel_model.resolution)
Example #8
0
def compute_isosurfaces(
    bands: Dict[Spin, np.ndarray],
    kpoint_dim: Tuple[int, int, int],
    fermi_level: float,
    reciprocal_space: ReciprocalCell,
    decimate_factor: Optional[float] = None,
    decimate_method: str = "quadric",
    smooth: bool = False,
) -> Dict[Spin, List[Tuple[np.ndarray, np.ndarray]]]:
    """
    Compute the isosurfaces at a particular energy level.

    Args:
        bands: The band energies, given as a dictionary of ``{spin: energies}``, where
            energies has the shape (nbands, nkpoints).
        kpoint_dim: The k-point mesh dimensions.
        fermi_level: The energy at which to calculate the Fermi surface.
        reciprocal_space: The reciprocal space representation.
        decimate_factor: If method is "quadric", factor is the scaling factor by which
            to reduce the number of faces. I.e., final # faces = initial # faces *
            factor. If method is "cluster", factor is the voxel size in which to
            cluster points. Default is None (no decimation).
        decimate_method: Algorithm to use for decimation. Options are "quadric" or
            "cluster".
        smooth: If True, will smooth resulting isosurface. Requires PyMCubes. Smoothing
            algorithm will use constrained smoothing algorithm to preserve fine details
            if input dimension is lower than (500, 500, 500), otherwise will apply a
            Gaussian filter.

    Returns:
        A dictionary containing a list of isosurfaces as ``(vertices, faces)`` for
        each spin channel.
    """
    rlat = reciprocal_space.reciprocal_lattice

    spacing = 1 / (np.array(kpoint_dim) - 1)

    if smooth and mcubes is None:
        smooth = False
        warnings.warn("Smoothing disabled, install PyMCubes to enable smoothing.")

    if decimate_factor is not None and open3d is None:
        decimate_factor = None
        warnings.warn("Decimation disabled, install open3d to enable decimation.")

    isosurfaces = {}
    for spin, ebands in bands.items():
        ebands -= fermi_level
        spin_isosurface = []

        for band in ebands:
            # check if band crosses fermi level
            if np.nanmax(band) > 0 > np.nanmin(band):
                band_data = band.reshape(kpoint_dim)

                if smooth:
                    #MDF_COMMENT trying smooth
                    # smoothing algorithm requires input to have surface at 0.5
                    #MDF_COMMENT actually the smooth algorith takes the whole range of 
                    #MDF_COMMENT values, you give the isosurface later, and the band_data is already 
                    #MDF_COMMENT translated to the fermi level. 
                    #MDF_COMMENT in the example at 
                    #MDF_COMMENT https://github.com/pmneila/PyMCubes/tree/b6ce8cc7a4e86c9b4b1bedea7073f573b6c7b739
                    # 0.5 is the level of the sphere they want to show, but you can set 
                    # whatever offset you want. you can also not set an offset and then ask
                    # for any other isolevel to the marching_cubes routine.
                    smoothed_band_data = mcubes.smooth(band_data) #-band_data.max() +0.5 )#MDF_COMMENT - 0.5)
                    # and outputs embedding array with values 0 and 1
                    verts, faces = mcubes.marching_cubes(smoothed_band_data, 0)
                    # have to manually set spacing with PyMCubes
                    verts = verts * spacing
                    # comes out as np.uint64, but trimesh doesn't like this
                    faces = faces.astype(np.int32)
                else:
                    verts, faces, _, _ = marching_cubes(band_data, 0, spacing=spacing)

                if decimate_factor:
                    verts, faces = decimate_mesh(
                        verts, faces, decimate_factor, method=decimate_method
                    )

                if isinstance(reciprocal_space, WignerSeitzCell):
                    verts = np.dot(verts - 0.5, rlat) * 3
                    verts, faces = _trim_surface(reciprocal_space, verts, faces)
                else:
                    # convert coords to cartesian
                    verts = np.dot(verts - 0.5, rlat)

                spin_isosurface.append((verts, faces))

        isosurfaces[spin] = spin_isosurface

    return isosurfaces
Example #9
0
def _calculate_band_isosurfaces(
    spin: Spin,
    band_idx: int,
    energies: np.ndarray,
    kpoint_dim: Tuple[int, int, int],
    spacing: np.ndarray,
    reference: np.ndarray,
    reciprocal_space: ReciprocalCell,
    decimate_factor: Optional[float],
    decimate_method: str,
    smooth: bool,
    calculate_dimensionality: bool,
    property_interpolator: Optional[LinearInterpolator],
):
    """Helper function to calculate the connected isosurfaces for a band."""
    from skimage.measure import marching_cubes

    from ifermi.analysis import (
        connected_subsurfaces,
        equivalent_surfaces,
        isosurface_dimensionality,
    )

    rlat = reciprocal_space.reciprocal_lattice

    if smooth and mcubes is None:
        smooth = False
        warnings.warn(
            "Smoothing disabled, install PyMCubes to enable smoothing.")

    if decimate_factor is not None and open3d is None:
        decimate_factor = None
        warnings.warn(
            "Decimation disabled, install open3d to enable decimation.")

    band_data = energies.reshape(kpoint_dim)

    if smooth:
        smoothed_band_data = mcubes.smooth(band_data)
        verts, faces = mcubes.marching_cubes(smoothed_band_data, 0)
        # have to manually set spacing with PyMCubes
        verts *= spacing
        # comes out as np.uint64, but trimesh doesn't like this
        faces = faces.astype(np.int32)
    else:
        verts, faces, _, _ = marching_cubes(band_data, 0, spacing=spacing)

    if decimate_factor:
        verts, faces = decimate_mesh(verts,
                                     faces,
                                     decimate_factor,
                                     method=decimate_method)

    verts += reference

    # break the isosurface into connected subsurfaces
    subsurfaces = connected_subsurfaces(verts, faces)

    if calculate_dimensionality:
        # calculate dimensionality of periodically equivalent surfaces.
        dimensionalities = {}
        mapping = equivalent_surfaces([s[0] for s in subsurfaces])
        for idx in mapping:
            dimensionalities[idx] = isosurface_dimensionality(
                *subsurfaces[idx])
    else:
        dimensionalities = None
        mapping = np.zeros(len(subsurfaces))

    isosurfaces = []
    dimensionality = None
    orientation = None
    properties = None
    for (subverts, subfaces), idx in zip(subsurfaces, mapping):
        # convert vertices to cartesian coordinates
        subverts = np.dot(subverts, rlat)
        subverts, subfaces = trim_surface(reciprocal_space, subverts, subfaces)

        if len(subverts) == 0:
            # skip surfaces that do not enter the reciprocal space boundaries
            continue

        if calculate_dimensionality:
            dimensionality, orientation = dimensionalities[idx]

        if property_interpolator is not None:
            properties = face_properties(property_interpolator, subverts,
                                         subfaces, band_idx, spin, rlat)

        isosurface = Isosurface(
            vertices=subverts,
            faces=subfaces,
            band_idx=band_idx,
            properties=properties,
            dimensionality=dimensionality,
            orientation=orientation,
        )
        isosurfaces.append(isosurface)
    return isosurfaces
Example #10
0
def npy2point(folder='ct_train', to_save='v', number_points=300, dim=3, crop_size=224, tocrop=False):
    """
    convert .npy to point cloud
    :param folder: the folder name of the data set
    :param to_save: choose which oen to save. 'v' represents vertices, 'p' represents plots, '' represents all.
    :param number_points: number of points for each point cloud
    :param dim: the dimension of the point clouds
    :param crop_size: the size of the cropped masks / gt
    :param tocrop: whether to crop the mask / gt
    :return:
    """
    assert to_save=='' or to_save=='v' or to_save=='p'
    import mcubes
    crop_from = 128 - crop_size//2
    crop_to = 128 + crop_size//2
    vertices_fold = os.path.join('../../input/PnpAda_release_data/', folder, 'vertices/')
    plots_fold = os.path.join('../../input/PnpAda_release_data/', folder, 'plots/')
    if not os.path.exists(vertices_fold):
        os.mkdir(vertices_fold)
    if not os.path.exists(plots_fold):
        os.mkdir(plots_fold)
    folder_path = os.path.join('../../input/PnpAda_release_data/', folder, "mask/")
    for path in tqdm(glob.glob(folder_path + '*.npy')):
        filename = os.path.splitext(os.path.basename(path))[0]
        vertices_path = os.path.join(vertices_fold, filename + '.npy')
        plot_path = os.path.join(plots_fold, filename + '.npy')
        if not os.path.exists(vertices_path):
            mask = np.load(path)
            if args.toplot:
                from matplotlib import pyplot as plt
                temp = mask[...,0][crop_from:crop_to, crop_from:crop_to] if tocrop else mask[...,0]
                plt.imshow(temp)
                plt.show()
            mask = np.where(mask > 0, 1, 0)
            mask = np.moveaxis(mask, -1, 0)
            if tocrop:
                mask = crop_volume(mask, crop_size=crop_size)
            mask = np.concatenate([mask, mask, mask], axis=0)
            point_cloud = np.zeros((crop_size, crop_size)) if tocrop else np.zeros((256, 256))
            vertices_array = np.zeros((number_points, dim))
            if mask.sum() > 50:
                vol = mcubes.smooth(mask)
                vertices, triangles = mcubes.marching_cubes(vol, 0)
                try:
                    vertices = graipher(vertices, number_points, dim=dim)
                except:
                    print(filename)
                    exit()
                vertices_array = np.array(vertices, dtype=np.int)
                if args.toplot:
                    fig = plt.figure()
                    from mpl_toolkits.mplot3d import Axes3D
                    ax = fig.add_subplot(111, projection='3d')
                    ax.scatter(vertices[:, 0], vertices[:, 1], vertices[:, 2], s=10)
                    plt.show()
                point_cloud[vertices_array[:,1], vertices_array[:,2]] = 1
                if args.toplot:
                    plt.imshow(point_cloud, cmap='gray')
                    plt.show()

            if to_save=='v' or to_save=='':
                np.save(vertices_path, vertices_array)
            if to_save=='p' or to_save=='':
                np.save(plot_path, point_cloud)
                # mcubes.export_mesh(vertices, triangles, "heart_single_slice.dae", "MyHeart_s")
    print("finish")
Example #11
0
def array2mesh(array, thresh=0., dim=3, coords=None, bbox=np.array([[-1,-1,-1],[1,1,1]]), return_coords=False, \
                if_decimate=False, decimate_face=4096, cart_coord=True, gaussian_sigma=None):
    """from 1-D array to 3D mesh

    Args:
        array (np.ndarray): 1-D array
        thresh (float, optional): threshold. Defaults to 0..
        dim (int, optional): 2 or 3, curve or mesh. Defaults to 3.
        coords (np.ndarray, optional): array's coordinates (num_points, x_dim). Defaults to None.
        bbox (np.ndarray, optional): bounding box of coords. Defaults to np.array([[-1,-1],[1,1]]).
        return_coords (bool, optional): whether return the coords. Defaults to False.
        decimate_face (int, optional): whether to simplify the mesh. Defaults to 4096.
        cart_coord (bool, optional): cartesian coordinate in array form, x->i, y->j,... and all varibles increases monotonically. Defaults to True.
        gaussian_sigma (float, optional): sigma value for gaussian filter (set None if there is no filter)
    Returns:
        tuple: `verts`, `faces`, `coords` or `verts`, `faces` according to `return_coords`
    """
    grid = nputil.array2NDCube(array, N=dim)

    if gaussian_sigma is not None:
        #from scipy.ndimage import gaussian_filter
        #grid = gaussian_filter(grid.astype(float), sigma=gaussian_sigma)
        grid = mcubes.smooth(grid)
    if dim == 3:
        verts, faces = mcubes.marching_cubes(grid, thresh)
        if cart_coord == False:
            verts = verts[:, [1, 0, 2]]
        verts = verts / (grid.shape[0] - 1)  # rearrange order and rescale
    elif dim == 2:
        contours = find_contours(grid, thresh)
        vcount, points, edges = 0, [], []
        for contour in contours:
            ind = np.arange(len(contour))
            points.append(contour)
            edges.append(np.c_[vcount + ind,
                               vcount + (ind + 1) % len(contour)])
            vcount += len(contour)
        if len(contours) == 0:
            return None, None
        verts = np.concatenate(points, axis=0)[:, [1, 0]] / (grid.shape[0] - 1)
        #verts = verts[:,[1,0]]
        faces = np.concatenate(edges, axis=0)
        #levelset_samples = igl.sample_edges(points, edges, 10)
    if coords is not None:
        bbmin, bbmax = nputil.arrayBBox(coords)
    else:
        bbmin, bbmax = bbox
        coords = nputil.makeGrid(bb_min=bbmin, bb_max=bbmax, shape=grid.shape)
    verts = verts * (bbmax - bbmin) + bbmin
    verts, faces = verts, faces.astype(int)
    if if_decimate == True:
        if dim != 3:
            print("Warning! decimation only works for 3D")
        elif faces.shape[0] > decimate_face:  # Only decimate when appropriate
            reached, verts, faces, _, _ = igl.decimate(verts, faces,
                                                       decimate_face)
            faces = faces.astype(int)
    if return_coords == True:
        return verts, faces, coords
    else:
        return verts, faces