def plot_ants_plane(off_screen=False, notebook=None):
    """
    Demonstrate how to create a plot class to plot multiple meshes while
    adding scalars and text.

    Plot two ants and airplane
    """

    # load and shrink airplane
    airplane = vtki.PolyData(planefile)
    airplane.points /= 10
    # pts = airplane.points # gets pointer to array
    # pts /= 10  # shrink

    # rotate and translate ant so it is on the plane
    ant = vtki.PolyData(antfile)
    ant.rotate_x(90)
    ant.translate([90, 60, 15])

    # Make a copy and add another ant
    ant_copy = ant.copy()
    ant_copy.translate([30, 0, -10])

    # Create plotting object
    plotter = vtki.Plotter(off_screen=off_screen, notebook=notebook)
    plotter.add_mesh(ant, 'r')
    plotter.add_mesh(ant_copy, 'b')

    # Add airplane mesh and make the color equal to the Y position
    plane_scalars = airplane.points[:, 1]
    plotter.add_mesh(airplane,
                     scalars=plane_scalars,
                     stitle='Plane Y\nLocation')
    plotter.add_text('Ants and Plane Example')
    plotter.plot()
Exemple #2
0
def test_invalid_file():
    with pytest.raises(Exception):
        mesh = vtki.PolyData('file.bad')

    with pytest.raises(TypeError):
        filename = os.path.join(test_path, 'test_polydata.py')
        mesh = vtki.PolyData(filename)
Exemple #3
0
def test_invalid_init():
    with pytest.raises(ValueError):
        mesh = vtki.PolyData(np.array([1]))

    with pytest.raises(TypeError):
        mesh = vtki.PolyData(np.array([1]), 'woa')

    with pytest.raises(TypeError):
        mesh = vtki.PolyData('woa', 'woa')

    with pytest.raises(TypeError):
        mesh = vtki.PolyData('woa', 'woa', 'woa')
Exemple #4
0
def test_init_from_arrays_triangular():
    vertices = np.array([[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0],
                         [0.5, 0.5, -1]])

    # mesh faces
    faces = np.vstack([[3, 0, 1, 2], [3, 0, 1, 4], [3, 1, 2, 4]])

    mesh = vtki.PolyData(vertices, faces)
    assert mesh.n_points == 5
    assert mesh.n_cells == 3

    mesh = vtki.PolyData(vertices, faces, deep=True)
    assert mesh.n_points == 5
    assert mesh.n_cells == 3
Exemple #5
0
def test_init_from_arrays():
    vertices = np.array([[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0],
                         [0.5, 0.5, -1]])

    # mesh faces
    faces = np.hstack([[4, 0, 1, 2, 3], [3, 0, 1, 4], [3, 1, 2,
                                                       4]]).astype(np.int8)

    mesh = vtki.PolyData(vertices, faces)
    assert mesh.n_points == 5
    assert mesh.n_cells == 3

    mesh = vtki.PolyData(vertices, faces, deep=True)
    vertices[0] += 1
    assert not np.allclose(vertices[0], mesh.points[0])
Exemple #6
0
    def add_border(self, color=[1, 1, 1], width=2.0):
        points = np.array([[1., 1., 0.],
                           [0., 1., 0.],
                           [0., 0., 0.],
                           [1., 0., 0.]])

        lines = np.array([[2, 0, 1],
                          [2, 1, 2],
                          [2, 2, 3],
                          [2, 3, 0]]).ravel()

        poly = vtki.PolyData()
        poly.points = points
        poly.lines = lines

        coordinate = vtk.vtkCoordinate()
        coordinate.SetCoordinateSystemToNormalizedViewport()

        mapper = vtk.vtkPolyDataMapper2D()
        mapper.SetInputData(poly);
        mapper.SetTransformCoordinate(coordinate);

        actor = vtk.vtkActor2D()
        actor.SetMapper(mapper)
        actor.GetProperty().SetColor(parse_color(color))
        actor.GetProperty().SetLineWidth(width)

        self.add_actor(actor)
Exemple #7
0
 def contour(self,
             surface,
             scalars,
             contours,
             line_width=1.0,
             opacity=1.0,
             vmin=None,
             vmax=None,
             colormap=None):
     from matplotlib import cm
     from matplotlib.colors import ListedColormap
     if colormap is None:
         cmap = cm.get_cmap('coolwarm')
     else:
         cmap = ListedColormap(colormap / 255.0)
     vertices = np.array(surface['rr'])
     triangles = np.array(surface['tris'])
     n_triangles = len(triangles)
     triangles = np.c_[np.full(n_triangles, 3), triangles]
     with warnings.catch_warnings():
         warnings.filterwarnings("ignore", category=FutureWarning)
         pd = vtki.PolyData(vertices, triangles)
         pd.point_arrays['scalars'] = scalars
         self.plotter.add_mesh(pd.contour(isosurfaces=contours,
                                          rng=(vmin, vmax)),
                               show_scalar_bar=False,
                               line_width=line_width,
                               cmap=cmap,
                               opacity=opacity)
Exemple #8
0
 def surface(self,
             surface,
             color=None,
             opacity=1.0,
             vmin=None,
             vmax=None,
             colormap=None,
             scalars=None,
             backface_culling=False):
     from matplotlib import cm
     from matplotlib.colors import ListedColormap
     if colormap is None:
         cmap = cm.get_cmap('coolwarm')
     else:
         cmap = ListedColormap(colormap / 255.0)
     vertices = np.array(surface['rr'])
     triangles = np.array(surface['tris'])
     n_triangles = len(triangles)
     triangles = np.c_[np.full(n_triangles, 3), triangles]
     with warnings.catch_warnings():
         warnings.filterwarnings("ignore", category=FutureWarning)
         pd = vtki.PolyData(vertices, triangles)
         if scalars is not None:
             pd.point_arrays['scalars'] = scalars
         self.plotter.add_mesh(mesh=pd,
                               color=color,
                               rng=[vmin, vmax],
                               show_scalar_bar=False,
                               opacity=opacity,
                               cmap=cmap,
                               backface_culling=backface_culling)
Exemple #9
0
def load_texture_to_omf(filename, name, description):
    """Loads a PNG Image texture to an ``omf.ImageTexture`` object"""
    # Load a raster
    import gdal
    ds = gdal.Open(filename.replace('.png', '.tif'))
    # Grab the Groung Control Points
    points = np.array([get_point(gcp) for gcp in ds.GetGCPs()])
    if points.size < 1:
        raise RuntimeError(
            'No associated tif file to recover spatial reference.')
    # Now Grab the three corners of their bounding box
    #-- This guarantees we grab the right points
    bounds = vtki.PolyData(points).bounds
    origin = np.array([bounds[0], bounds[2], bounds[4]])  # BOTTOM LEFT CORNER
    point_u = np.array([bounds[1], bounds[2],
                        bounds[4]])  # BOTTOM RIGHT CORNER
    point_v = np.array([bounds[0], bounds[3], bounds[4]])  # TOP LEFT CORNER
    axis_u = point_u - origin
    axis_v = point_v - origin
    the_texture = omf.ImageTexture(
        origin=origin,
        axis_u=axis_u,
        axis_v=axis_v,
        name=name,
        description=description,
        image=filename,
    )
    return the_texture
Exemple #10
0
def read(filename):
    """This will read any VTK file! It will figure out what reader to use
    then wrap the VTK object for use in ``vtki``
    """
    def legacy(filename):
        reader = vtk.vtkDataSetReader()
        reader.SetFileName(filename)
        reader.Update()
        return reader.GetOutputDataObject(0)

    ext = os.path.splitext(filename)[1]
    if ext.lower() in '.vtk':
        # Use a legacy reader and wrap the result
        return wrap(legacy(filename))
    else:
        # From the extension, decide which reader to use
        if ext.lower() in '.vti':  # ImageData
            return vtki.UniformGrid(filename)
        elif ext.lower() in '.vtr':  # RectilinearGrid
            return vtki.RectilinearGrid(filename)
        elif ext.lower() in '.vtu':  # UnstructuredGrid
            return vtki.UnstructuredGrid(filename)
        elif ext.lower() in '.ply':  # PolyData
            return vtki.PolyData(filename)
        elif ext.lower() in '.vts':  # UnstructuredGrid
            return vtki.StructuredGrid(filename)
        else:
            # Attempt to use the legacy reader...
            try:
                return wrap(legacy(filename))
            except:
                pass
    raise IOError("This file was not able to be automatically read by vtki.")
Exemple #11
0
def test_init_as_points():
    vertices = np.array([[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0],
                         [0.5, 0.5, -1]])

    mesh = vtki.PolyData(vertices)
    assert mesh.n_points == vertices.shape[0]
    assert mesh.n_cells == vertices.shape[0]
Exemple #12
0
def lines_from_points(points):
    """
    Generates line from points.  Assumes points are ordered as line segments.

    Parameters
    ----------
    points : np.ndarray
        Points representing line segments.  For example, two line segments
        would be represented as:

        np.array([[0, 0, 0], [1, 0, 0], [1, 0, 0], [1, 1, 0]])

    Returns
    -------
    lines : vtki.PolyData
        PolyData with lines and cells.

    Examples
    --------
    This example plots two line segments at right angles to each other line.

    >>> import vtki
    >>> import numpy as np
    >>> points = np.array([[0, 0, 0], [1, 0, 0], [1, 0, 0], [1, 1, 0]])
    >>> lines = vtki.lines_from_points(points)
    >>> lines.plot() # doctest:+SKIP

    """
    # Assuming ordered points, create array defining line order
    npoints = points.shape[0] - 1
    lines = np.vstack((2 * np.ones(npoints, np.int), np.arange(npoints),
                       np.arange(1, npoints + 1))).T.ravel()

    return vtki.PolyData(points, lines)
Exemple #13
0
def test_save(extension, binary, tmpdir):
    filename = str(tmpdir.mkdir("tmpdir").join('tmp.%s' % extension))
    sphere.save(filename, binary)

    mesh = vtki.PolyData(filename)
    assert mesh.faces.shape == sphere.faces.shape
    assert mesh.points.shape == sphere.points.shape
Exemple #14
0
 def mesh(self, x, y, z, triangles, color, opacity=1.0, shading=False,
          backface_culling=False, **kwargs):
     vertices = np.c_[x, y, z]
     n_triangles = len(triangles)
     triangles = np.c_[np.full(n_triangles, 3), triangles]
     with warnings.catch_warnings():
         warnings.filterwarnings("ignore", category=FutureWarning)
         pd = vtki.PolyData(vertices, triangles)
         self.plotter.add_mesh(mesh=pd, color=color, opacity=opacity,
                               backface_culling=backface_culling)
Exemple #15
0
def vector_poly_data(orig, vec):
    """ Creates a vtkPolyData object composed of vectors """

    # shape, dimention checking
    if not isinstance(orig, np.ndarray):
        orig = np.asarray(orig)

    if not isinstance(vec, np.ndarray):
        vec = np.asarray(vec)

    if orig.ndim != 2:
        orig = orig.reshape((-1, 3))
    elif orig.shape[1] != 3:
        raise Exception('orig array must be 3D')

    if vec.ndim != 2:
        vec = vec.reshape((-1, 3))
    elif vec.shape[1] != 3:
        raise Exception('vec array must be 3D')

    # Create vtk points and cells objects
    vpts = vtk.vtkPoints()
    vpts.SetData(numpy_to_vtk(np.ascontiguousarray(orig), deep=True))

    npts = orig.shape[0]
    cells = np.hstack((np.ones((npts, 1), 'int'), np.arange(npts).reshape(
        (-1, 1))))

    if cells.dtype != ctypes.c_int64 or cells.flags.c_contiguous:
        cells = np.ascontiguousarray(cells, ctypes.c_int64)
    cells = np.reshape(cells, (2 * npts))
    vcells = vtk.vtkCellArray()
    vcells.SetCells(npts, numpy_to_vtkIdTypeArray(cells, deep=True))

    # Create vtkPolyData object
    pdata = vtk.vtkPolyData()
    pdata.SetPoints(vpts)
    pdata.SetVerts(vcells)

    # Add vectors to polydata
    name = 'vectors'
    vtkfloat = numpy_to_vtk(np.ascontiguousarray(vec), deep=True)
    vtkfloat.SetName(name)
    pdata.GetPointData().AddArray(vtkfloat)
    pdata.GetPointData().SetActiveVectors(name)

    # Add magnitude of vectors to polydata
    name = 'mag'
    scalars = (vec * vec).sum(1)**0.5
    vtkfloat = numpy_to_vtk(np.ascontiguousarray(scalars), deep=True)
    vtkfloat.SetName(name)
    pdata.GetPointData().AddArray(vtkfloat)
    pdata.GetPointData().SetActiveScalars(name)

    return vtki.PolyData(pdata)
def test_export_verts(tmpdir):
    filename = str(tmpdir.mkdir("tmpdir").join('scene'))
    data = vtki.PolyData(np.random.rand(100, 3))
    # Create the scene
    plotter = vtki.Plotter(off_screen=OFF_SCREEN)
    plotter.add_mesh(data)
    plotter.export_vtkjs(filename)
    cpos_out = plotter.show()  # Export must be called before showing!
    plotter.close()
    # Now make sure the file is there
    assert os.path.isfile('{}.vtkjs'.format(filename))
Exemple #17
0
def with_vtk(plot=True):
    """ Tests VTK interface and mesh repair of Stanford Bunny Mesh """
    mesh = vtki.PolyData(bunny_scan)
    meshfix = pymeshfix.MeshFix(mesh)
    if plot:
        print('Plotting input mesh')
        meshfix.Plot()
    meshfix.Repair()
    if plot:
        print('Plotting repaired mesh')
        meshfix.Plot()

    return meshfix.mesh
Exemple #18
0
def test_contour():
    dataset = examples.load_uniform()
    iso = dataset.contour()
    assert iso is not None
    iso = dataset.contour(isosurfaces=[100, 300, 500])
    assert iso is not None
    with pytest.raises(AssertionError):
        result = dataset.contour(scalars='Spatial Cell Data')
    with pytest.raises(RuntimeError):
        result = dataset.contour(isosurfaces=vtki.PolyData())
    dataset = examples.load_airplane()
    with pytest.raises(AssertionError):
        result = dataset.contour()
Exemple #19
0
 def sphere(self, center, color, scale, opacity=1.0,
            resolution=8, backface_culling=False):
     sphere = vtk.vtkSphereSource()
     sphere.SetThetaResolution(resolution)
     sphere.SetPhiResolution(resolution)
     sphere.Update()
     geom = sphere.GetOutput()
     with warnings.catch_warnings():
         warnings.filterwarnings("ignore", category=FutureWarning)
         pd = vtki.PolyData(center)
         self.plotter.add_mesh(pd.glyph(orient=False, scale=False,
                                        factor=scale, geom=geom),
                               color=color, opacity=opacity,
                               backface_culling=backface_culling)
Exemple #20
0
def test_load_and_save_file(tmpdir):
    meshfix = _meshfix.PyTMesh()
    meshfix.load_file(examples.bunny_scan)

    with pytest.raises(Exception):
        meshfix.load_file(examples.bunny_scan)

    v, f = meshfix.return_arrays()
    assert f.shape[0] == bunny.n_faces

    # test saving
    filename = str(tmpdir.mkdir("tmpdir").join('tmp.ply'))
    meshfix.save_file(filename)
    new_bunny = vtki.PolyData(filename)
    assert new_bunny.points.shape == v.shape
Exemple #21
0
def test_repair_vtk():
    meshin = vtki.PolyData(bunny_scan)
    meshfix = pymeshfix.MeshFix(meshin)
    meshfix.repair()

    # check arrays and output mesh
    assert np.any(meshfix.v)
    assert np.any(meshfix.f)
    meshout = meshfix.mesh
    assert meshfix.mesh.n_points

    # test for any holes
    pdata = meshout.extract_edges(non_manifold_edges=False, feature_edges=False,
                                  manifold_edges=False)
    assert pdata.n_points == 0
Exemple #22
0
def read(filename, attrs=None):
    """This will read any VTK file! It will figure out what reader to use
    then wrap the VTK object for use in ``vtki``.

    Parameters
    ----------
    attrs : dict, optional
        A dictionary of attributes to call on the reader. Keys of dictionary are
        the attribute/method names and values are the arguments passed to those
        calls. If you do not have any attributes to call, pass ``None`` as the
        value.
    """
    filename = os.path.abspath(os.path.expanduser(filename))
    ext = get_ext(filename)

    # From the extension, decide which reader to use
    if attrs is not None:
        reader = get_reader(filename)
        return standard_reader_routine(reader, filename, attrs=attrs)
    elif ext in '.vti':  # ImageData
        return vtki.UniformGrid(filename)
    elif ext in '.vtr':  # RectilinearGrid
        return vtki.RectilinearGrid(filename)
    elif ext in '.vtu':  # UnstructuredGrid
        return vtki.UnstructuredGrid(filename)
    elif ext in ['.ply', '.obj', '.stl']:  # PolyData
        return vtki.PolyData(filename)
    elif ext in '.vts':  # StructuredGrid
        return vtki.StructuredGrid(filename)
    elif ext in ['.vtm', '.vtmb']:
        return vtki.MultiBlock(filename)
    elif ext in ['.e', '.exo']:
        return read_exodus(filename)
    elif ext in ['.vtk']:
        # Attempt to use the legacy reader...
        return read_legacy(filename)
    else:
        # Attempt find a reader in the readers mapping
        try:
            reader = get_reader(filename)
            return standard_reader_routine(reader, filename)
        except KeyError:
            pass
    raise IOError("This file was not able to be automatically read by vtki.")
Exemple #23
0
def load_attach_texture(dataset, filename, name):
    """Loads a texture and attaches it to the dataset inplace.
    """
    import gdal
    # Load a raster
    ds = gdal.Open(filename)
    texture = vtki.load_texture(filename)
    # Grab the Groung Control Points
    points = np.array([get_point(gcp) for gcp in ds.GetGCPs()])
    # Now Grab the three corners of their bounding box
    #-- This guarantees we grab the right points
    bounds = vtki.PolyData(points).bounds
    origin = [bounds[0], bounds[2], bounds[4]]  # BOTTOM LEFT CORNER
    point_u = [bounds[1], bounds[2], bounds[4]]  # BOTTOM RIGHT CORNER
    point_v = [bounds[0], bounds[3], bounds[4]]  # TOP LEFT CORNER
    dataset.texture_map_to_plane(origin,
                                 point_u,
                                 point_v,
                                 inplace=True,
                                 name=name)
    dataset.textures[name] = texture
    return None  # No return because it updates input object inplace
Exemple #24
0
def test_texture():
    """Test adding texture coordinates"""
    # create a rectangle vertices
    vertices = np.array([
        [0, 0, 0],
        [1, 0, 0],
        [1, 0.5, 0],
        [0, 0.5, 0],
    ])

    # mesh faces
    faces = np.hstack([[3, 0, 1, 2], [3, 0, 3, 2]]).astype(np.int8)

    # Create simple texture coordinates
    t_coords = np.array([[0, 0], [1, 0], [1, 1], [0, 1]])
    # Create the poly data
    mesh = vtki.PolyData(vertices, faces)
    # Attempt setting the texture coordinates
    mesh.t_coords = t_coords
    # now grab the texture coordinates
    foo = mesh.t_coords
    assert np.allclose(foo, t_coords)
Exemple #25
0
def img2D(points):
    #三角形片面重建模型
    sg.Popup('渲染正在进行中,请您耐心等候一段时间~~~',
             button_color=('white', 'blue'),
             background_color='white',
             keep_on_top=True)
    gif(gifpath2)

    cloud = vtki.PolyData(points)
    #     bcloud = vtki.PolyData(blist)
    #     roicloud = vtki.PolyData(roilist)
    surf = cloud.delaunay_2d(tol=1e-08,
                             alpha=1,
                             offset=100.0,
                             bound=False,
                             inplace=False)
    #     surf1 = bcloud.delaunay_2d(tol = 1e-08,alpha = 0.3,offset = 100.0,bound = False,inplace =False )
    #     surf2 = roicloud.delaunay_2d(tol = 1e-08,alpha = 0.1,offset = 100.0,bound = False,inplace =False )
    print(type(surf))
    plotter = vtki.BackgroundPlotter()
    plotter.add_mesh(surf)
    #     plotter.add_mesh(surf1)
    #     plotter.add_mesh(surf2)
    plotter.show()
Exemple #26
0
import vtki
import numpy as np

################################################################################
#  First, create some points for the surface.

# Define a simple Gaussian surface
xx, yy = np.meshgrid(np.linspace(-200,200,20), np.linspace(-200,200,20))
A, b = 100, 100
zz = A*np.exp(-0.5*((xx/b)**2. + (yy/b)**2.))

# Get the points as a 2D NumPy array (N by 3)
points = np.c_[xx.reshape(-1), yy.reshape(-1), zz.reshape(-1)]
print(points[0:5, :])

################################################################################
# Now use those points to create a point cloud ``vtki`` data object. This will
# be encompassed in a :class:`vtki.PolyData` object.

# simply pass the numpy points to the PolyData constructor
cloud = vtki.PolyData(points)
vtki.set_plot_theme('doc')
cloud.plot(point_size=15)

################################################################################
# Now that we have a ``vtki`` data structure of the points, we can perform a
# triangulation to turn those boring discrete points into a connected surface.

surf = cloud.delaunay_2d()
surf.plot(show_edges=True)
Exemple #27
0
def test_init():
    mesh = vtki.PolyData()
    assert not mesh.n_points
    assert not mesh.n_cells
Exemple #28
0
def test_init_from_pdata():
    mesh = vtki.PolyData(sphere, deep=True)
    assert mesh.n_points
    assert mesh.n_cells
    mesh.points[0] += 1
    assert not np.allclose(sphere.points[0], mesh.points[0])
Exemple #29
0
 def mesh(self):
     """ Return the surface mesh """
     triangles = np.empty((self.f.shape[0], 4))
     triangles[:, -3:] = self.f
     triangles[:, 0] = 3
     return vtki.PolyData(self.v, triangles, deep=False)
Exemple #30
0
def triangulation(pts,
                  downsampling=(1, 1, 1),
                  n_closings=0,
                  single_cc=False,
                  decimate_mesh=0,
                  gradient_direction='descent',
                  force_single_cc=False):
    """
    Calculates triangulation of point cloud or dense volume using marching cubes
    by building dense matrix (in case of a point cloud) and applying marching
    cubes.

    Parameters
    ----------
    pts : np.array
        [N, 3] or [N, M, O] (dtype: uint8, bool)
    downsampling : Tuple[int]
        Magnitude of downsampling, e.g. 1, 2, (..) which is applied to pts
        for each axis
    n_closings : int
        Number of closings applied before mesh generation
    single_cc : bool
        Returns mesh of biggest connected component only
    decimate_mesh : float
        Percentage of mesh size reduction, i.e. 0.1 will leave 90% of the
        vertices
    gradient_direction : str
        defines orientation of triangle indices. '?' is needed for KNOSSOS
         compatibility. TODO: check compatible index orientation, switched to `descent`, 23April2019
    force_single_cc : bool
        If True, performans dilations until only one foreground CC is present
        and then erodes with the same number to maintain size.

    Returns
    -------
    array, array, array
        indices [M, 3], vertices [N, 3], normals [N, 3]

    """
    if boundaryDistanceTransform is None:
        raise ImportError(
            '"boundaryDistanceTransform" could not be imported from VIGRA. '
            'Please install vigra, see SyConn documentation.')
    assert type(
        downsampling) == tuple, "Downsampling has to be of type 'tuple'"
    assert (pts.ndim == 2 and pts.shape[1] == 3) or pts.ndim == 3, \
        "Point cloud used for mesh generation has wrong shape."
    if pts.ndim == 2:
        if np.max(pts) <= 1:
            msg = "Currently this function only supports point " \
                  "clouds with coordinates >> 1."
            log_proc.error(msg)
            raise ValueError(msg)
        offset = np.min(pts, axis=0)
        pts -= offset
        pts = (pts / downsampling).astype(np.uint32)
        # add zero boundary around object
        margin = n_closings + 5
        pts += margin
        bb = np.max(pts, axis=0) + margin
        volume = np.zeros(bb, dtype=np.float32)
        volume[pts[:, 0], pts[:, 1], pts[:, 2]] = 1
    else:
        volume = pts
        if np.any(np.array(downsampling) != 1):
            ndimage.zoom(volume, downsampling, order=0)
        offset = np.array([0, 0, 0])
    if n_closings > 0:
        volume = binary_closing(volume,
                                iterations=n_closings).astype(np.float32)
        if force_single_cc:
            n_dilations = 0
            while True:
                labeled, nb_cc = ndimage.label(volume)
                # log_proc.debug('Forcing single CC, additional dilations {}, num'
                #                'ber connected components: {}'
                #                ''.format(n_dilations, nb_cc))
                if nb_cc == 1:  # does not count background
                    break
                # pad volume to maintain margin at boundary and correct offset
                volume = np.pad(volume, [(1, 1), (1, 1), (1, 1)],
                                mode='constant',
                                constant_values=0)
                offset -= 1
                volume = binary_dilation(volume,
                                         iterations=1).astype(np.float32)
                n_dilations += 1
    else:
        volume = volume.astype(np.float32)
    if single_cc:
        labeled, nb_cc = ndimage.label(volume)
        cnt = Counter(labeled[labeled != 0])
        l, occ = cnt.most_common(1)[0]
        volume = np.array(labeled == l, dtype=np.float32)
    # InterpixelBoundary, OuterBoundary, InnerBoundary
    dt = boundaryDistanceTransform(volume, boundary="InterpixelBoundary")
    dt[volume == 1] *= -1
    volume = gaussianSmoothing(dt, 1)
    if np.sum(volume < 0) == 0 or np.sum(volume > 0) == 0:  # less smoothing
        volume = gaussianSmoothing(dt, 0.5)
    try:
        verts, ind, norm, _ = measure.marching_cubes_lewiner(
            volume, 0, gradient_direction=gradient_direction)
    except Exception as e:
        raise ValueError(e)
    if pts.ndim == 2:  # account for [5, 5, 5] offset
        verts -= margin
    verts = np.array(verts) * downsampling + offset
    if decimate_mesh > 0:
        if not __vtk_avail__:
            msg = "vtki not installed. Please install vtki.'" \
                  "pip install vtki'."
            log_proc.error(msg)
            raise ImportError(msg)
        # log_proc.warning("'triangulation': Currently mesh-sparsification"
        #                  " may not preserve volume.")
        # add number of vertices in front of every face (required by vtki)
        ind = np.concatenate(
            [np.ones((len(ind), 1)).astype(np.int64) * 3, ind], axis=1)
        mesh = vtki.PolyData(verts, ind.flatten())
        mesh.decimate(decimate_mesh, volume_preservation=True)
        # remove face sizes again
        ind = mesh.faces.reshape((-1, 4))[:, 1:]
        verts = mesh.points
        mo = MeshObject("", ind, verts)
        # compute normals
        norm = mo.normals.reshape((-1, 3))
    return np.array(ind, dtype=np.int), verts, norm