Source code for porespy.generators._fractals
import logging
import numpy as np
import scipy.ndimage as spim
from porespy import settings
from porespy.tools import get_tqdm
tqdm = get_tqdm()
logger = logging.getLogger(__name__)
__all__ = [
'random_cantor_dust',
'sierpinski_foam',
]
[docs]
def random_cantor_dust(
shape, n: int = 5,
p: int = 2,
f: float = 0.8,
seed: int = None,
):
r"""
Generates an image of random cantor dust
Parameters
----------
shape : array_like
The shape of the final image. If not evenly divisible by $p**n$
it will be increased to the nearest size that is.
n : int
The number of times to iteratively divide the image.
p : int (default = 2)
The number of divisions to make on each iteration.
f : float (default = 0.8)
The fraction of the set to keep on each iteration.
seed : int, optional, default = `None`
Initializes numpy's random number generator to the specified state. If not
provided, the current global value is used. This means calls to
``np.random.state(seed)`` prior to calling this function will be respected.
Returns
-------
dust : ndarray
A boolean image of a random Cantor dust
Examples
--------
`Click here
<https://porespy.org/examples/generators/reference/randon_cantor_dust.html>`_
to view online example.
"""
if seed is not None:
np.random.seed(seed)
# Parse the given shape and adjust if necessary
shape = np.array(shape)
trim = np.mod(shape, (p**n))
if np.any(trim > 0):
shape = shape - trim + p**n
logger.warning(f"Requested shape being changed to {shape}")
im = np.ones(shape, dtype=bool)
divs = []
if isinstance(n, int):
for i in range(1, n):
divs.append(p**i)
else:
for i in n:
divs.append(p**i)
for i in tqdm(divs, **settings.tqdm):
sh = (np.array(im.shape)/i).astype(int)
mask = np.random.rand(*sh) < f
mask = spim.zoom(mask, zoom=i, order=0)
im = im*mask
return im
[docs]
def sierpinski_foam(
shape,
n: int = 5,
mode: str = 'upper',
):
r"""
Generates an image of a Sierpinski carpet or foam with independent control of
image size and number of iterations
Parameters
----------
shape : array_like
The shape of the final image to create. To create a full image with no
cropping, use a that is a multiple of `3**n`.
n : int
The number of times to iteratively divide the image. This functions starts
by inserting single voxels, then inserts increasingly large squares/cubes.
mode : str
Controls the portion of the image that is returned, options are `'upper'`
which returns the upper corner, `'centered'`, which returns the center
portion of the image, and `None` provide the full image, in which case
the returned image will be larger than `shape`.
Returns
-------
im : ndarray
A boolean image with `False` values inserted at the center of each
square (or cubic) sub-section.
Examples
--------
`Click here
<https://porespy.org/examples/generators/reference/sierpinski_foam.html>`_
to view online example.
"""
m = n
if 3**(n+1)//3 < max(shape):
while 3**(m+1)//3 < max(shape):
m += 1
im = np.zeros([3**(m+1)//3 for _ in range(len(shape))], dtype=bool)
i = 0
pbar = tqdm()
while i < n:
if im.ndim == 2:
mask = np.zeros([3**(i+1), 3**(i+1)], dtype=bool)
s = 3**(i+1)//3
mask[s:-s, s:-s] = 1
t = int(np.ceil(im.shape[0]/mask.shape[0]))
im2 = np.tile(mask, [t, t])
if im.ndim == 3:
mask = np.zeros([3**(i+1), 3**(i+1), 3**(i+1)], dtype=bool)
s = 3**(i+1)//3
mask[s:-s, s:-s, s:-s] = 1
t = int(np.ceil(im.shape[0]/mask.shape[0]))
im2 = np.tile(mask, [t, t, t])
im += im2
i += 1
pbar.update()
pbar.close()
if mode is None:
slices = [...]
elif mode == 'centered':
slices = [slice(im.shape[ax]//2 - shape[ax]//2,
im.shape[ax]//2 + shape[ax]//2,
None) for ax in range(im.ndim)]
elif mode == 'upper':
slices = [slice(0, shape[ax], None) for ax in range(im.ndim)]
im = im[tuple(slices)]
im = im == 0 # Invert image
return im