upxo.gbops.grainBoundOps3d module

Module: grainBoundOps3d

Grain-boundary operations for 3D labelled voxel structures.

Provides fast, Numba-accelerated routines for detecting grain-boundary voxels in 3-D labelled feature images (LFI) and for identifying topologically problematic voxels (articulation points) that violate manifold connectivity.

Functions

compute_gb_boundary_mask_interiorVoxels(lfi)

Detect interior boundary voxels using 6-connectivity (Numba parallel).

compute_gb_boundary_mask(lfi)

Extend interior boundary mask to include the full RVE outer shell.

identify_articulation_voxels(lfi)

Flag voxels whose removal would disconnect their local grain neighbourhood.

Usage

import upxo.gbops.grainBoundOps3d as gbOps

@author: Dr. Sunil Anandatheertha

upxo.gbops.grainBoundOps3d.compute_gb_boundary_mask_interiorVoxels(lfi)

Detect interior grain-boundary voxels using 6-connectivity.

A voxel is classified as a boundary voxel when at least one of its six face-adjacent neighbours (±x, ±y, ±z) carries a different grain ID. Voxels on the outermost shell of the array are excluded; use compute_gb_boundary_mask() to include those.

The loop over the x-axis is parallelised with prange; inner loops remain serial so that Numba can apply vectorisation.

Parameters:

lfi (numpy.ndarray of int, shape (nx, ny, nz)) – 3-D array of integer grain (feature) IDs.

Returns:

boundary – Boolean mask where True marks a grain-boundary voxel. The outermost shell (index 0 and n-1 in each axis) is always False — use compute_gb_boundary_mask() to set it.

Return type:

numpy.ndarray of bool, shape (nx, ny, nz)

Notes

Decorated with @njit(parallel=True): the function is JIT-compiled by Numba on the first call and cached for subsequent calls. It is called internally by compute_gb_boundary_mask(); direct use is only needed when the RVE outer faces should be excluded from the boundary mask.

Usage

import upxo.gbops.grainBoundOps3d as gbOps

Examples

import numpy as np
import upxo.gbops.grainBoundOps3d as gbOps

lfi = np.zeros((10, 10, 10), dtype=np.int32)
lfi[5:, :, :] = 1          # two-grain RVE split at x=5
mask = gbOps.compute_gb_boundary_mask_interiorVoxels(lfi)
# voxels at x=4 and x=5 that face the split are True
print(mask[4, 5, 5], mask[5, 5, 5])  # True True
upxo.gbops.grainBoundOps3d.compute_gb_boundary_mask(lfi)[source]

Compute a grain-boundary mask that includes the RVE outer shell.

Calls compute_gb_boundary_mask_interiorVoxels() to detect all interior boundary voxels (6-connectivity criterion), then sets every voxel on the six outer faces of the bounding box to True. This represents the physical convention that grains touching the RVE surface are bounded by that surface.

Parameters:

lfi (numpy.ndarray of int, shape (nx, ny, nz)) – 3-D array of integer grain (feature) IDs.

Returns:

boundary – Boolean mask where True marks either an interior grain-boundary voxel or a voxel on the outer RVE shell.

Return type:

numpy.ndarray of bool, shape (nx, ny, nz)

Notes

The outer shell assignment is O(n²) per face and does not require Numba. The dominant cost is the parallel interior scan in compute_gb_boundary_mask_interiorVoxels().

Usage

import upxo.gbops.grainBoundOps3d as gbOps

Examples

import numpy as np
import upxo.gbops.grainBoundOps3d as gbOps

lfi = np.zeros((10, 10, 10), dtype=np.int32)
lfi[5:, :, :] = 1
mask = gbOps.compute_gb_boundary_mask(lfi)
# All six outer faces are True
assert mask[0, :, :].all()
assert mask[-1, :, :].all()
# Interior boundary plane is also True
assert mask[4, 5, 5] and mask[5, 5, 5]
upxo.gbops.grainBoundOps3d.identify_articulation_voxels(lfi)[source]

Identify voxels whose removal disconnects their local grain neighbourhood.

For every boundary voxel, the function extracts the 3×3×3 neighbourhood and checks whether the same-grain voxels within that stencil remain 6-connected after the centre voxel is excluded. If they split into more than one connected component the centre voxel is an articulation point: removing it (e.g. during surface reconstruction or mesh smoothing) would create a topological hole in the grain.

Parameters:

lfi (numpy.ndarray of int, shape (nx, ny, nz)) – 3-D array of integer grain (feature) IDs.

Returns:

articulation_mask – Boolean mask where True marks a voxel whose removal would disconnect the local 26-neighbourhood same-grain region under 6-connectivity.

Return type:

numpy.ndarray of bool, shape (nx, ny, nz)

Notes

Complexity is O(N_boundary) × O(1) per voxel (the 3×3×3 stencil is constant size), so runtime scales with the total grain-boundary area rather than the full volume.

A simplified x-shift is used for the initial boundary pre-filter: only voxels where lfi[i,j,k] != lfi[i-1,j,k] are considered. This may miss some boundary voxels detected by the full 6-neighbour check in compute_gb_boundary_mask_interiorVoxels(), but reduces unnecessary 6-connectivity tests on clearly interior voxels.

The connected-component labelling is performed by cc3d.connected_components with connectivity=6.

Usage

import upxo.gbops.grainBoundOps3d as gbOps

Examples

import numpy as np
import upxo.gbops.grainBoundOps3d as gbOps

lfi = np.zeros((20, 20, 20), dtype=np.int32)
lfi[10:, :, :] = 1
art = gbOps.identify_articulation_voxels(lfi)
# For a clean planar boundary no articulation voxels should be present
print("Articulation voxels found:", art.sum())

See also

compute_gb_boundary_mask

Identify all grain-boundary voxels.