# Creating surfaces#

In this notebook we create a surface (mesh) from a simulated 3D binary image dataset.

import napari_process_points_and_surfaces as nppas
import pyclesperanto_prototype as cle
import vedo

from branchoid import branchoid

binary_image = branchoid()
binary_image

 shape (100, 100, 100) dtype uint8 size 976.6 kB min 0 max 1

## Generating surfaces#

We first generate a surface from the binary image. In this case, we take all non-zero labeled pixels and turn them into a surface.

surface = nppas.all_labels_to_surface(binary_image)


The resulting object is visualized in Jupyter notebooks like this:

surface

nppas.SurfaceTuple
 origin (z/y/x) [0. 0. 0.] center of mass(z/y/x) 50.000,46.575,42.589 scale(z/y/x) 1.000,1.000,1.000 bounds (z/y/x) 25.500...74.5002.500...88.5002.500...83.500 average size 31.277 number of vertices 19040 number of faces 38076

Technically, it is a tuple.

isinstance(surface, tuple)

True


The tuple contains vertices and faces.

vertices, faces = surface


Vertices are lists of lists of Z/Y/X coordinates in 3D space.

vertices

array([[25.5, 44. , 47. ],
[26. , 43.5, 47. ],
[26. , 44. , 46.5],
...,
[74.5, 56. , 51. ],
[74.5, 56. , 52. ],
[74.5, 56. , 53. ]], dtype=float32)


Faces are lists of lists of indices. Every triangle has three point coordinates indexed like this:

faces

array([[    2,     1,     0],
[    4,     3,     0],
[    4,     0,     1],
...,
[19038, 18870, 18872],
[19038, 18872, 19039],
[19039, 18872, 18852]], dtype=int64)


## Surfaces from individual labels#

If we have a label image as starting point, we can also turn individual objects into surfaces.

labels = cle.voronoi_otsu_labeling(binary_image, spot_sigma=6)
labels

cle._ image
 shape (100, 100, 100) dtype uint32 size 3.8 MB min 0.0 max 2.0
nppas.largest_label_to_surface(labels)

nppas.SurfaceTuple
 origin (z/y/x) [0. 0. 0.] center of mass(z/y/x) 50.000,53.374,48.714 scale(z/y/x) 1.000,1.000,1.000 bounds (z/y/x) 25.000...75.00025.000...89.00012.000...84.000 average size 28.026 number of vertices 8278 number of faces 16552
nppas.label_to_surface(labels, label_id=1)

nppas.SurfaceTuple
 origin (z/y/x) [0. 0. 0.] center of mass(z/y/x) 50.000,18.136,18.136 scale(z/y/x) 1.000,1.000,1.000 bounds (z/y/x) 36.000...64.0002.000...38.0002.000...38.000 average size 15.361 number of vertices 2472 number of faces 4952
nppas.label_to_surface(labels, label_id=2)

nppas.SurfaceTuple
 origin (z/y/x) [0. 0. 0.] center of mass(z/y/x) 50.000,53.374,48.714 scale(z/y/x) 1.000,1.000,1.000 bounds (z/y/x) 25.000...75.00025.000...89.00012.000...84.000 average size 28.026 number of vertices 8278 number of faces 16552

## Creating surfaces using vedo#

Vedo also offers functions for creating surfaces such as iso_surface().

volume = vedo.Volume(binary_image)

iso_surface = volume.isosurface()
iso_surface


vedo.mesh.Mesh
 bounds (x/y/z) 25.33 ... 74.672.333 ... 88.672.333 ... 83.67 center of mass (50.0, 46.6, 42.6) average size 31.358 nr. points / faces 19040 / 38076 point data array input_scalars

The resulting data structure is a vedo Mesh. You can access its points and faces as well.

iso_surface.points()

array([[49.       , 11.       ,  2.3333333],
[50.       , 11.       ,  2.3333333],
[51.       , 11.       ,  2.3333333],
...,
[50.       , 55.       , 83.666664 ],
[51.       , 55.       , 83.666664 ],
[52.       , 55.       , 83.666664 ]], dtype=float32)

iso_surface.faces()[:10]

[[0, 92, 104],
[0, 1, 93],
[92, 0, 93],
[1, 2, 94],
[93, 1, 94],
[94, 2, 105],
[3, 106, 118],
[3, 4, 107],
[106, 3, 107],
[104, 107, 4]]


## Exercise#

Load the skimage.data.cells3d dataset, extract the second channel and create a surface mesh from the nuclei.