Structure of porous solids

The easiest visualization that can be created with PoreScene is a representation of the 3D structure of a porous material (no matter if it is on nano-, micro- or centimeter length scale). The data used for this type of visualization is usually tomographic data, which is often available as a binarized 3D voxel image.

To create a visualization of the material, this example shows how to generate a mesh of the voxel data, load it into the scene, and render it to a PNG with a transparent background.

As an example, this tutorial uses a binarized volume of a freeze-dried sugar solution that was captured with X-ray micro-computed tomographic imaging [1], which is available in PoreScene’s repository on GitHub: data/img_bin.raw

Solid structure of a freeze-dried sugar solution captured with X-ray micro-computed tomographic imaging

The rendered result: the solid phase of a freeze-dried sugar solution, captured with X-ray micro-computed tomography.

Step-by-step guide

1. Import required modules

Make sure to have porescene installed (see Installation). At first, required modules need to be imported:

from pathlib import Path

import numpy as np

from porescene import io, utility
from porescene.scene import Scene

Module utility provides a function that converts the binarized volume image into a mesh, which can be subsequently saved with functions from io in OBJ or PLY formats. Scene loads and renders the created mesh. File paths and directories are handled throughout PoreScene with the built-in pathlib module.

2. Set utility variables

After that, a directory for saving the mesh file and the rendered images is specified:

# data subdirectory
pth_data = Path.cwd() / "data"

Next, some required information about the volume image is saved:

# [m] edge length of a single voxel
L_vxl = 1e-6

# [vxl] image resolution
res_img = np.array((100, 100, 100))

# [m] domain dimensions
extent = res_img * L_vxl

extent is the physical extent of the volume in meters – here 100 voxels of 1 µm along each edge. It calibrates the axis ticks and sets the aspect ratio of the scene, so it always stays in meters regardless of the unit you display (see Concepts & Conventions).

3. Mesh the binarized volume

The binarized volume data is loaded from file and reshaped into its original resolution. volume2mesh() turns the binarized volume into a mesh (every matrix element in img_bin with a value of 1 gets included). mesh2ply() writes the result to disk. You can skip this step in case you already have a geometric object file of your solid.

# load and reshape binarized volume image
img_bin = np.fromfile(pth_data / "img_bin.raw", dtype=np.uint8)
img_bin = img_bin.reshape(res_img)

# mesh representation of the volume image
mesh = utility.volume2mesh(img_bin, L_vxl, name="solid")

# export the mesh in binary PLY format
io.mesh2ply(pth_data / "solid.ply", mesh)

Tip

Use mesh2obj() instead to write an OBJ file. PoreScene also imports .stl, .abc, .usd, .fbx and .glb/.gltf meshes from other tools.

4. Scene setup

Scene initializes the rendering stage with camera and lighting. The scene is empty by default – every visible element is added explicitly in the next steps.

# initialize a new scene
sc = Scene(extent)

For the rendering, two components need to be added to the Scene: the previously generated mesh of the solid and axes around it:

# add axes to the scene
sc.create_axes()

# add a solid object to the scene
sc.create_solid(pth_data / "solid.ply")

# render the scene
pth_img = sc.render(pth_data / "solid+axes.png")

With the final line, Blender renders the scene and saves the image as solid+axes.png in the given data directory.

Full script

The complete example, also available on GitHub: example/solid.py.

example/solid.py
 1from pathlib import Path
 2
 3import numpy as np
 4
 5from porescene import io, utility
 6from porescene.scene import Scene
 7
 8# =============================================================================
 9# Parameters
10
11# data subdirectory
12pth_data = Path.cwd() / "data"
13
14# [m] edge length of a single voxel
15L_vxl = 1e-6
16
17# [vxl] image resolution
18res_img = np.array((100, 100, 100))
19
20# [m] domain dimensions
21extent = res_img * L_vxl
22
23
24# =============================================================================
25# Meshing
26
27# load and reshape binarized volume image
28img_bin = np.fromfile(pth_data / "img_bin.raw", dtype=np.uint8)
29img_bin = img_bin.reshape(res_img)
30
31# mesh representation of the volume image
32mesh = utility.volume2mesh(img_bin, L_vxl, name="solid")
33
34# export the mesh in binary PLY format
35io.mesh2ply(pth_data / "solid.ply", mesh)
36
37
38# =============================================================================
39# Scene configuration and rendering
40
41# initialize a new scene
42sc = Scene(extent)
43
44# add axes to the scene
45sc.create_axes()
46
47# add a solid object to the scene
48sc.create_solid(pth_data / "solid.ply")
49
50# render the scene
51pth_img = sc.render(pth_data / "solid+axes.png")

References