hub/venv/lib/python3.7/site-packages/trimesh/voxel/morphology.py

192 lines
5.4 KiB
Python

"""Basic morphology operations that create new encodings."""
import numpy as np
from . import encoding as enc
from . import ops
from ..constants import log_time
from .. import util
try:
from scipy.ndimage import morphology as _m
except BaseException as E:
# scipy is a soft dependency
from ..exceptions import ExceptionModule
_m = ExceptionModule(E)
def _dense(encoding, rank=None):
if isinstance(encoding, np.ndarray):
dense = encoding
elif isinstance(encoding, enc.Encoding):
dense = encoding.dense
else:
raise ValueError(
'encoding must be np.ndarray or Encoding, got %s' % str(encoding))
if rank:
_assert_rank(dense, rank)
return dense
def _sparse_indices(encoding, rank=None):
if isinstance(encoding, np.ndarray):
sparse_indices = encoding
elif isinstance(encoding, enc.Encoding):
sparse_indices = encoding.sparse_indices
else:
raise ValueError(
'encoding must be np.ndarray or Encoding, got %s' % str(encoding))
_assert_sparse_rank(sparse_indices, 3)
return sparse_indices
def _assert_rank(value, rank):
if len(value.shape) != rank:
raise ValueError(
'Expected rank %d, got shape %s' % (rank, str(value.shape)))
def _assert_sparse_rank(value, rank=None):
if len(value.shape) != 2:
raise ValueError(
'sparse_indices must be rank 2, got shape %s' % str(value.shape))
if rank is not None:
if value.shape[-1] != rank:
raise ValueError(
'sparse_indices.shape[1] must be %d, got %d'
% (rank, value.shape[-1]))
@log_time
def fill_base(encoding):
"""
Given a sparse surface voxelization, fill in between columns.
Parameters
--------------
encoding: Encoding object or sparse array with shape (?, 3)
Returns
--------------
A new filled encoding object.
"""
return enc.SparseBinaryEncoding(
ops.fill_base(_sparse_indices(encoding, rank=3)))
@log_time
def fill_orthographic(encoding):
"""
Fill the given encoding by orthographic projection method.
Any voxel in the dense representation with no free ray along the x, y, z
axes in each direction is assigned filled. This is likely faster than fill
holes, and is more stable with regards to small holes.
Parameters
--------------
encoding: Encoding object or dense rank-3 array.
Returns
--------------
A new filled encoding object.
"""
return enc.DenseEncoding(ops.fill_orthographic(_dense(encoding, rank=3)))
@log_time
def fill_holes(encoding, **kwargs):
"""
Encoding wrapper around scipy.ndimage.morphology.binary_fill_holes.
https://docs.scipy.org/doc/scipy-0.15.1/reference/generated/scipy.ndimage.morphology.binary_fill_holes.html#scipy.ndimage.morphology.binary_fill_holes
Parameters
--------------
encoding: Encoding object or dense rank-3 array.
**kwargs: see scipy.ndimage.morphology.binary_fill_holes.
Returns
--------------
A new filled in encoding object.
"""
return enc.DenseEncoding(
_m.binary_fill_holes(_dense(encoding, rank=3), **kwargs))
fillers = util.FunctionRegistry(
base=fill_base,
orthographic=fill_orthographic,
holes=fill_holes,
)
def fill(encoding, method='base', **kwargs):
"""
Fill the given encoding using the specified implementation.
See `fillers` for available implementations or to add your own, e.g. via
`fillers['custom_key'] = custom_fn`.
`custom_fn` should have signature `(encoding, **kwargs) -> filled_encoding`
and should not modify encoding.
Parameters
--------------
encoding: Encoding object (left unchanged).
method: method present in `fillers`.
**kwargs: additional kwargs passed to the specified implementation.
Returns
--------------
A new filled Encoding object.
"""
return fillers(method, encoding=encoding, **kwargs)
def binary_dilation(encoding, **kwargs):
"""
Encoding wrapper around scipy.ndimage.morphology.binary_dilation.
https://docs.scipy.org/doc/scipy-0.15.1/reference/generated/scipy.ndimage.morphology.binary_dilation.html#scipy.ndimage.morphology.binary_dilation
"""
return enc.DenseEncoding(
_m.binary_dilation(_dense(encoding, rank=3), **kwargs))
def binary_closing(encoding, **kwargs):
"""
Encoding wrapper around scipy.ndimage.morphology.binary_closing.
https://docs.scipy.org/doc/scipy-0.15.1/reference/generated/scipy.ndimage.morphology.binary_closing.html#scipy.ndimage.morphology.binary_closing
"""
return enc.DenseEncoding(
_m.binary_closing(_dense(encoding, rank=3), **kwargs))
def surface(encoding, structure=None):
"""
Get elements on the surface of encoding.
A surface element is any one in encoding that is adjacent to an empty
voxel.
Parameters
--------------
encoding: Encoding or dense rank-3 array
structure: adjacency structure. If None, square connectivity is used.
Returns
--------------
new surface Encoding.
"""
dense = _dense(encoding, rank=3)
# padding/unpadding resolves issues with occupied voxels on the boundary
dense = np.pad(dense, np.ones((3, 2), dtype=int), mode='constant')
empty = np.logical_not(dense)
dilated = _m.binary_dilation(empty, structure=structure)
surface = np.logical_and(dense, dilated)[1:-1, 1:-1, 1:-1]
return enc.DenseEncoding(surface)