Synthetic Image Cases¶
voids.generators.porous_image provides package-level helpers for constructing
controlled 2-D and 3-D binary porous images. These are intended for testing,
algorithm development, and reproducible demonstrations before moving to
scanner-derived data.
The central convention is:
Truemeans void,Falsemeans solid or matrix,- all shapes follow NumPy axis order.
Macro/Micro Pore Images¶
The macro/micro generator builds a two-porosity synthetic image from two PoreSpy
blobs fields:
- generate a resolved macropore image,
- generate a finer-scale micropore score field,
- keep micropores only inside the solid matrix of the macropore image,
- combine the two masks.
from voids.generators import generate_macro_micro_blobs_matrix
case = generate_macro_micro_blobs_matrix(
shape=(160, 160, 160),
macro_porosity=0.25,
matrix_microporosity=0.12,
macro_blobiness=1.2,
micropore_blobiness=8.0,
axis_index=0,
seed_start=2026,
max_tries=50,
)
void = case.void
macro = case.macro_void
micropores = case.micropore_void
The invariant is:
The reported total porosity is:
where \(\phi_{\mathrm{micro|matrix}}\) is the micropore fraction measured only in the matrix region of the macro image.
The micropore_blobiness parameter controls the feature scale of the small
pores. In PoreSpy blobs, larger blobiness values produce smaller features,
so a typical macro/micro setup uses:
- lower
macro_blobinessfor broader connected macropores, - higher
micropore_blobinessfor smaller pores in the matrix.
Synthetic interpretation
The micropore mask is a controlled synthetic model, not a calibrated unresolved-porosity law. It is useful for algorithm sensitivity studies, but the values should not be interpreted as a measured carbonate microporosity field without calibration.
Vug Insertions¶
Existing helpers can add idealized circular, elliptical, spherical, or ellipsoidal vugs to a binary matrix:
from voids.generators import insert_spherical_vug
with_vug, vug_mask = insert_spherical_vug(
case.void,
radius_vox=18,
center=(80, 80, 80),
)
The operation is a boolean union, so existing pores remain void.
For sweeps that specify vug support as a fraction of the complete voxel volume, use a centered superellipsoid:
from voids.generators import insert_centered_superellipsoidal_vug
with_vug, vug_mask = insert_centered_superellipsoidal_vug(
case.void,
volume_fraction=0.40,
exponent=2.5,
margin_vox=1,
)
The mask contains
round(volume_fraction * case.void.size) voxels. An exponent of 2 is
spherical; larger exponents progressively approach a rounded cube. This matters
for high fractions: an enclosed sphere can occupy no more than
\(\pi/6\approx0.524\) of a cube. The generator rejects a requested fraction
that cannot fit inside the configured exponent and boundary margin instead of
silently clipping it. When the requested count intersects a tied outer score
shell, deterministic tie selection preserves the exact voxel count but can
break perfect reflection symmetry only on that final shell.
The executed
52_mwe_synthetic_3d_matrix_centered_vug_volume_fraction
notebook demonstrates a \(300^3\), 10 µm sweep through vug fraction 0.60 and
includes a live central-slice atlas.
Export And Import¶
Synthetic cases are ordinary voids image volumes once generated. Export and
import them through the dedicated
Image Volume And Surface Mesh I/O
section, which documents VolumeData, supported voxel formats, STL/OBJ surface
exports, sidecar metadata, and explicit voxel-size handling.
A typical synthetic-case export wraps the generated void mask with physical resolution metadata before writing files:
from voids.io import VolumeData, save_volume_bundle
case_data = VolumeData(
with_vug,
voxel_size=(40.0e-6, 40.0e-6, 40.0e-6),
units={"length": "m"},
metadata=case.metadata,
)
written = save_volume_bundle(
case_data,
"outputs/synthetic_case",
stem="macro_micro_vug",
formats=("raw", "npy", "h5", "nc", "tiff", "stl", "obj"),
)
Read the exported data with load_volume_data when physical resolution matters: